I'm trying to define a recursive function that returns X if the given input's square sum leads to 1, which works fine. However, if the input doesn't lead to 1 then it keeps looping and I cannot find a way to exit it.
For instance, input of integer 7, 10, 13 leads to 1, but input of 2, 3, 4, 5, 6, 11 does not. If I try
x === 4 || x === 0
, it ends the recursion for the input that does not lead to 1 but for some inputs it takes multiple recursion calls to reach to 4 and that's not efficient. Later on, I want to use the return values for other calculations.
function recursion(x) {
if (x === 0) {
return;
}
if (x === 1) {
return x;
}
x = squareSum(x);
return recursion(x);
}
Here is the squareSum function.
function sqaureSum(n){
let sumTotal;
if (n < 10){
sumTotal = Math.pow(n, 2);
return sumTotal;
}
sumTotal = Math.pow(n % 10, 2) + squareSum(Math.floor(n / 10));
return sumTotal;
}
You need to track the numbers you've already seen, stopping when you reach 1 or when you reach a number you've already seen.
We can add a default parameter with a list of already-seen numbers, and then in a recursive call, add the current number to that list. This version just returns a boolean, true if the number eventually reaches 1 and false if we hit some other cycle. And we find the numbers among the first hundred positive integers, which do reduce to 1:
const squareSum = (n) =>
(n) < 10 ? n * n : (n % 10) ** 2 + squareSum (Math .floor ( n / 10))
const repeatedSquareSum = (n, seen = []) =>
n <= 1
? true
: seen .includes (n)
? false
: repeatedSquareSum (squareSum (n), [...seen, n])
console .log (Array .from ({length: 100}, (n, i) => i + 1) .filter (n => repeatedSquareSum(n)))
You wanted to return the original number in the positive case. That's odd, but not much more difficult:
const repeatedSquareSum = (n, seen = []) =>
n <= 1
? seen [0] || n
: seen .includes (n)
? false
: repeatedSquareSum (squareSum (n), [...seen, n])
(The || n bit is just because we haven't yet added anything to seen in the cases of 0 and 1.)
You also could return the array of numbers visited in the false case, by returning seen instead of false in that case, or if you just want to see the repeating cycle of numbers, you could return [...seen .slice (seen.indexOf (n)), n].
Other notes have pointed out that there is only one cycle of repeating values, outside of 0 -> 0 and 1 -> 1. And that is 4 -> 16 -> 37 -> 58 -> 89 -> 145 -> 42 -> 20 -> 4. This somewhat surprising result is proven in a fairly readable formal mathematical paper. Using that information, we can choose to write an even simpler version:
const repeatedSquareSum = (n) =>
n <= 1
? true
: n == 4
? false
: repeatedSquareSum (squareSum (n))
Update
A followup question in the comments (next time, please start a new question) asked how to count the number of steps until we reach a target number such as 20. We can add a default parameter with the count, and track until we reach that number or enter a loop. The only complexity is that if the target is in the list of numbers that cycle endlessly, we need to not stop when we enter the loop but only when we hit the target number.
Here we build this on the last version above for simplicity, but we could also do so atop the earlier, more complex versions:
const squareSum = (n) =>
(n) < 10 ? n * n : (n % 10) ** 2 + squareSum (Math .floor ( n / 10))
const squareSumToTarget = (target) => (n, count = 0) =>
n <= 1 || (![4, 16, 37, 58, 89, 145, 42, 20] .includes (target) && n == 4)
? Infinity
: n == target
? count
: squareSumToTarget (target) (squareSum (n), count + 1)
const squareSumTo20 = squareSumToTarget (20)
console .log (squareSumTo20 (445566)) //=> 3 (445566 -> 154 -> 42 -> 20)
console .log (squareSumTo20 (44)) //=> Infinity (44 -> 32 -> 13 -> 10 -> 1 -> 1 -> 1 -> ...)
console .log (squareSumTo20 (4)) //=> 7 (4 -> 16 -> 37 -> 58 -> 89 -> 145 -> 42 -> 20)
console .log (squareSumToTarget (17) (79)) //=> Infinity (79 -> 130 -> 10 -> 1 -> 1 -> 1 -> ...)
console .log (squareSumToTarget (36) (5) )
//=> Infinity (36 -> 45 -> 41 -> 17 -> 50 -> 25 -> 29 -> 85 -> 89 -> 145 -> 42 -> 20 -> 4 -> 16 -> 37 -> 58 -> 89 ...)
// ^----------------------------------------------'
because of sumTotal = Math.pow(n % 10, 2) + squareSum(n / 10);
when you calculate n / 10 it's can be a float, and you should convert your numbers to int
for checking float numbers log x inside recursion
function recursion(x) {
console.log(x) // <-- HERE
...
}
Math.floor, Math.ceil, Math.round can help you
and for
if (x === 0) {
return;
}
inside recursion add a value ( 0 or 1 ).
it's return undefiend and it convert to NaN
Searching a few minuts, I found this util information. Summarizing, you can check if the number you get, in some point is equal to any of these numbers: 4, 16, 37, 58, 89, 145, 42, 20:
const loopNumbers = new Set([4, 16, 37, 58, 89, 145, 42, 20]);
...
if(loopNumbers.has(x)) {
// It does not lead to 1
}
Related
I had to find all the factors of positive number that evenly divide into a number and then return a p^th element of the list, sorted ascending.
If there is no p^th return 0.
I tested almost all the answers i could solve and found online:
Example:
function pthFactor(n, k) {
let arr = [];
for (let i = 1; i <= n; i++) {
if (n % i === 0) {
arr.push(i);
}
if (arr.length === k) {
return arr[arr.length - 1];
}
}
if (arr.length !== k) {
return 1;
}
};
or
var kthFactor = function(n, k) {
let factors = [1]
for(let i=2;i<=Math.floor(n/2);i++){
if(n%i == 0) factors.push(i)
}
factors.push(n)
return factors.length < k?-1:factors[k-1]
};
buts its failing 10 sec time limit.
What i am doing wrong ?
By the way i also tried Math.sqrt etc in order not to loop n times. Didn't work as well.
Do i need to know more than for loop ? Like dynamic programming etc to solve this ?
I couldn't find this challenge on HackerRank, but the 1492. The kth Factor of n challenge on LeetCode seems to be the same as you describe:
Given two positive integers n and k.
A factor of an integer n is defined as an integer i where n % i == 0.
Consider a list of all factors of n sorted in ascending order, return the kth factor in this list or return -1 if n has less than k factors.
It is strange that you used the name pthFactor in your first code block, while the name of the relevant argument is k and not p. That is quite confusing.
I also tried Math.sqrt etc in order not to loop n times
You cannot only consider factors up to the square root of 𝑛. For instance, 6 is a factor of 12, and even 12 is a factor of 12.
However, about half of all factors are in the range up to the square root. And those factors that are greater, are equal to the quotients found by performing the divisions with those smaller factors. So in the end, you can stop at the square root, provided that you collect all those quotients, and pick the right one from those if you didn't arrive at the 𝑘th factor yet.
So here is how that idea could be implemented:
var kthFactor = function(n, k) {
let bigFactors = [];
let quotient = n + 1;
for (let factor = 1; factor < quotient; factor++) {
if (n % factor === 0) {
quotient = n / factor;
k--;
if (k <= 0) return factor;
if (factor >= quotient) break;
bigFactors.push(quotient);
}
}
return bigFactors[bigFactors.length - k] ?? -1;
};
// A few test cases:
console.log(kthFactor(12, 3)); // 3
console.log(kthFactor(7, 2)); // 7
console.log(kthFactor(16, 5)); // 16
console.log(kthFactor(27, 5)); // -1
console.log(kthFactor(927, 1)); // 1
This code uses the same algorithm as in trincot's answer, but I think it's expressed more simply with recursion:
const kthFactor = (n, k, f = 1, r = []) =>
f * f > n
? r [k - 1] ?? -1
: n % f == 0
? k == 1 ? f : kthFactor (n, k - 1, f + 1, f * f == n ? r : [n / f, ...r])
: kthFactor (n, k, f + 1, r)
console .log (kthFactor (12, 3)); //=> 3
console .log (kthFactor (7, 2)); //=> 7
console .log (kthFactor (16, 5)); //=> 16
console .log (kthFactor (27, 5)); //=> -1
console .log (kthFactor (927, 1)); //=> 1
Here we track the factor we're testing (f) and the remaining numbers (r) to be tested, decreasing k whenever we find a new factor and adding the quotient n / f to our remaining numbers, increasing f on every step, stopping when we've gotten too big (f * f > n) and then finding the index in the ordered list of remaining numbers, returning the factor when k is 1, and simply recurring with an incremented factor otherwise.
The only tricky bit is the handling of perfect squares, where the square root should only be counted once, so we don't prepend it to the remaining numbers if f * f == n.
Because of the recursion, this is only suitable for a k so large. While we could make this tail recursive in a simple manner, it doesn't matter much as of this writing, since tail call optimization has still not happened in most engines.
const dice = [1,3,4,5,6]
const score = straightScore(dice,4)
function straightScore(dice, sizeOfStraight) {
let score = 0
sizeOfStraight > 4 ? score = 40 : score = 30
dice.sort( (a, b) => a-b )
// filter to eliminate duplicates
const filteredDice = dice.reduce( (acc, die) => {
if ( acc.indexOf(die) === -1 ) acc.push(die)
return acc
}, [] )
//determine straight
const straightFinder = filteredDice.reduce( (acc, die) => {
const lastNumInArray = acc.slice(-1)[0]
// here is my hack if the small straight starts with 3 and the last die is a 1
if ( die - lastNumInArray === 2) acc = [die]
if ( lastNumInArray === undefined || die - lastNumInArray === 1 ) acc.push(die)
return acc
}, [] )
if (straightFinder.length >= sizeOfStraight) return score
return 0
}
console.log(score)
I got this to work, but it feels hacky. Any thoughts would be appreciated.
I am trying to make a basic Yahtzee game. Determining if a straight was rolled is where I got stuck. It basically worked, but if it was a small straight (4 in a row out of five dice) going from 3 - 6 AND the fifth die was a 1 my reduce would not work right. It always resolved to a 1 item array. I can see the problem in my logic, so I threw a little hack in there to make it work, but I feel like there must be a dozen better ways. Here is my code. Thanks for looking.
The dice are an array of 5 numbers 1-6 representing dice, the sizeOfStraight is just so I can reuse it for the large straight. The problem only came up with the small straight though, so you could just put a 4 in for that.
input dice = [1, 3, 4, 5, 6]
output (none, just returns because straightFinder.length = 1)
You don't really need reduce in that final operation. Just check the sorted dice at index 0 with index 3. If the latter has a value of 3 more than the first, the first 4 sorted dice represent a small straight. The same can be repeated at index 1 and index 4. There are no other positions possible for a small straight. A large straight can only start at index 0, so that only needs one check.
NB: extracting unique values can be done with the help of a temporary Set.
const dice = [1, 3, 4, 5, 6];
const score = straightScore(dice, 4);
function straightScore(dice, sizeOfStraight) {
let score = 0;
sizeOfStraight > 4 ? score = 40 : score = 30;
dice.sort( (a, b) => a-b );
// duplicates can be eliminated with a Set:
const filteredDice = [...new Set(dice)];
// determine straight: only two locations are of interest (at most)
if (filteredDice[sizeOfStraight-1] - filteredDice[0] === sizeOfStraight-1) return score;
if (filteredDice[sizeOfStraight] - filteredDice[1] === sizeOfStraight-1) return score;
return 0;
}
console.log(score);
Maybe this answer is a little overkill for Your needs, but may inspire You. Generally, I would create a "histogram" of rolled values and then look for the longest sequence of consecutive "hits" to find the longest straight.
Approach 1:
Let's say, we rolled dice = [6, 3, 1, 4, 3]. The "histogram" would look like this:
0 - false (there's no dice roll 0)
1 - true
2 - false
3 - true
4 - true
5 - false
6 - true
7 - false (there's no dice roll 7)
Indexes 0 and 7 are useful as boundaries, which are guaranteed to be always false (which simpifies detecting straights starting at 1 or ending at 6). It can be created like this:
Array(8).fill(false).map((_, i) => dice.indexOf(i) >= 0)
-> [false, true, false, true, true, false, true, false]
Now we need to find length of the longest sequence of trues. Let's traverse through the histogram and detect changes from false to true (straight start) and back (straight end):
function findStraights(dice) {
const histogram = Array(8).fill(false).map((_, i) => dice.indexOf(i) >= 0)
let straights = [];
let start = 0;
for (let i = 1; i < 8; i++) {
if (histogram[i - 1] === histogram[i])
continue;
if (histogram[i])
start = i
else
straights.push({ start: start, length: i - start })
}
return straights
}
console.log(findStraights([]))
console.log(findStraights([5]))
console.log(findStraights([3, 2, 1]))
console.log(findStraights([4, 5, 6, 1]))
console.log(findStraights([6, 3, 1, 4, 3]))
Now we have array of all found straights and finding the longest one is trivial (sort(...)[0]).
Approach 2:
Let's represent the histogram as a binary number (instead of the array shown above) and use a little bit-twiddling magic to determine longest sequence length:
function findLongestStraight(dice) {
let bits = dice.reduce((a, v) => a | (1 << v), 0)
let longest = 0
while (bits !== 0) {
bits &= bits << 1
longest++
}
return longest
}
console.log(findLongestStraight([]))
console.log(findLongestStraight([5]))
console.log(findLongestStraight([3, 2, 1]))
console.log(findLongestStraight([4, 5, 6, 1]))
console.log(findLongestStraight([6, 3, 1, 4, 3]))
The first line creates binary number bits, where every bit represents if given number was rolled on at least one die (bit 0 is always 0, bit 1 represents rolled 1 etc.) In our case bits = 0b01011010 = 90.
The while part uses a bit-twiddling trick, that shortens every sequence of consecutive 1s by one on every iteration. All we do is count number of iterations needed to zero all sequeces.
this is an assignment on Codewars, in other words like a homework. :)
I have to write a function that returns the amount of numbers between 1 and n (inclusive) that can be represented as the difference of two perfect squares. For example, 20 = 6² - 4² and 21 = 5² - 2². Many numbers can be written this way, but not all.
I wrote a function, it works well, but it needs to be able to handle n values up to 45000. Basically my code crashes when it gets to analyze numbers in the order of thousands. In order to make the code more efficient, I tried to reverse the initial loop, from n to 0 instead than from 0 to n. I have tried to divide the n by two until it gets small enough and then multiply the final result times 2 again, but didn't work. I also used a while loop, but then I realized that I simply don't know how to solve this issue, and after 3 days of pointless attempts to solve it with brute force, I am asking for help as I don't want to just give up on it . This is my code
function countSquareable(n){
var y = []
var c = []
for (var i = 0; i <= n; i++) { // all numbers powered in range
y.push(Math.pow(i,2))
}
for(i = 0; i < y.length; i++) {
c.push(y.map(a => y[i]-a)) // all subtractions' combos
}
var d = c.toString().split(",").sort(function(a, b){return a-b}).filter(function(a) {return a>0 && a<=n}) // only the combos I need in the range
var a = [], b = [], prev; // remove duplicates
d.sort();
for ( var i = 0; i < d.length; i++ ) {
if ( d[i] !== prev ) {
a.push(d[i]);
b.push(1);
} else {
b[b.length-1]++;
}
prev = d[i];
}
return console.log(a.length) // end result
};
countSquareable(500)
countSquareable(4) // should return 3 and it works
countSquareable(5) // should return 4 and it works
countSquareable(40) // should return 30 and it works
countSquareable(45000) // should return 33750 but crashes
countSquareable(6427), // should return 4820 but crashes
How do I make a code SO MUCH more efficient to solve the issue?
The kata is here.
thanks!!
This could do with a small dose of math.
If instead of counting the values, you can list them, say the thirty of them for 40, you get
[1, 3, 4, 5, 7, 8, 9, 11, 12, 13, 15, 16, 17, 19, 20, 21,
23, 24, 25, 27, 28, 29, 31, 32, 33, 35, 36, 37, 39, 40]
If it's difficult to see the pattern there, try reading it aloud. The point is that these group easily as
[ 1,
3, 4, 5,
7, 8, 9,
11, 12, 13,
15, 16, 17,
19, 20, 21,
23, 24, 25,
27, 28, 29,
31, 32, 33,
35, 36, 37,
39, 40, (41)]
In other words, every fourth number is missing, starting with 2. Here's code that follows that pattern:
const range = (lo, hi) => Array(...Array(hi - lo + 1)).map((_, n) => lo + n)
const countSquareable = (n) => range(1, n).filter(n => n % 4 !== 2).length
console.log(countSquareable(4))
console.log(countSquareable(5))
console.log(countSquareable(40))
console.log(countSquareable(45000))
So it matches the expectations. But we're in the territory of math now, so we need to prove things. We can do this in three cases:
Can n be represented as the difference of squares?
Case 1: n is odd
Let a = (n + 1) / 2, let b = (n - 1) / 2.
Since n is odd, n - 1 and n + 1 are even, so a and b are both integers.
a^2 = (n^2 + 2n + 1) / 4
b^2 = (n^2 - 2n + 1) / 4
so
a^2 - b^2 = 4n / 4 = n
Hence, an odd number can be represented as the difference of squares.
Case 2: n is divisible by 4
Let a = (n / 4 + 1), let b = (n / 4 - 1)
Since n is divisible by 4, (n / 4) is an integer and thusaandb` are integers.
Now
a^2 = (n^2/16 + 2n/4 + 1)
b^2 = (n^2/16 - 2n/4 + 1)
and
a^2 - b^2 = 4n/4 = n
Hence, a multiple of 4 can be represented as the difference of squares.
Case 3: A multiple of 2 that's not a multiple of 4
We can divide the integers up this way: (4n), (4n + 1), (4n + 2), (4n + 3).
squaring each of these, and choosing an appropriate k we get:
(4n)^2 = 16n^2 = 4 * 4n^2 = (4k)
(4n + 1)^2 = (16n^2 + 8n + 1) = 4(4n^2 + 2n) + 1, = (4k + 1)
(4n + 2)^2 = (16n^2 + 16n + 4) = 4(4n^2 + 4n + 1) = (4k)
(4n + 3)^2 = (16n^2 + 24n + 9) = 4(4n^2 + 6n + 2) + 1 = (4k + 1)
So the only remainders possible when dividing a square by 4 are 0 and 1.
Subtracting those, we get (1 - 0) = 1, (1 - 1) = 0, (0 - 0) = 0, (0 - 1) = -1 (this last is the same as a remainder of 3: 4k - 1 = 4(k -1) + 3.)
So we can get remainders of 0, 1, or 3. But we cannot get 2.
Hence, a multiple of 2 that's not a multiple of 4 cannot be represented as the difference of squares.
Q.E.D. We've proven our intuition correct: Any integer can be written as the difference of squares except for those that are multiples of 2 but not of 4.
Update
My original code was a brute-force approach, noting that a^2 - b^2 = (a - b) * (a + b) and that hence the smaller factor ((a - b)) had to be less than the square root of our top number if the product would be less than n. Then I tried all the possible values of b, saving (a^2 - b^2) if it was less than n. This works, and seems efficient enough for the 45000 case. But it misses the analysis above.
In any case, here is that version:
const countSquareable = (n) => {
const found = new Set()
// a^2 - b^2 = (a - b)(a + b), so (a - b) can be no larger than sqrt(n)
const topDiff = Math.sqrt(n)
for (let diff = 1; diff <= topDiff; diff++) {
const topB = n / 2 // can we get a tighter bound here?
for (let b = 0; b < topB; b++) {
let a = b + diff
const val = (a * a) - (b * b)
if (val <= n) {
found.add(val)
}
}
}
//console.log([...found].sort((a, b) => a - b))
return found.size
}
console.log(countSquareable(45000))
This is for Project Euler, problem #5.
The task is to find the smallest number evenly divisible by numbers 1-20. My code seems to work on 1-18, but at 19 my browser starts timing out. This leads me to believe my code is just inefficient.
How can I mitigate this?
function divisible(a){
counter = 0;
result = 2;
while (counter < a){
for (var x = 0; x <= a; x ++){
if (result % x === 0){
counter ++;
}
}
if (counter != a){
counter = 0;
result ++;
}
}
return result;
}
divisible(20)
Basically, you want the least common multiple of 1,...,20.
I would implement lcm by using gcd, which can be implemented using the fast Euclidean algorithm.
function gcd(a, b) {
return b === 0 ? a : gcd(b, a%b); // Euclidean algorithm
}
function lcm(a, b) {
return a * b / gcd(a, b);
}
function divisible(a){
var result = 1;
for(var i=2; i<=a; ++i)
result = lcm(result, i);
return result;
}
divisible(20); // 232792560
Yup, inefficient. You would need to change the algorithm. The most efficient I can think of is to factorise all the numbers from 2 to 20 (with factors and counts: e.g. 18 is 3 * 3 * 2, or twice 3 and once 2, for final { 3: 2, 2: 1 }); then find the maximum for each factor, and multiply them together.
An abbreviated example: the least number that is divisible by 18 and 16:
18: { 3: 2, 2: 1 }
16: { 2: 4 }
maximums of factor repetitions: { 3: 2, 2: 4 }
result: 3^2 * 2^4 = 144
Factorising numbers from 2 to 20 is easy; if you don't know how to do it, there are many possible algorithms, you can see the Wikipedia article on integer factorisation for ideas.
another option with brute force and modulo rest-classification
this problem can be solved with a simple common modulo rest class characteristics.
look at the numbers from 1 to 20 and divide it into two groups and find some unique common attributes between them.
1 2 3 4 5 6 7 8 9 10
we are building a division with the same reminder members
1 divides all
2 divide 4,8 -->>8 important
3 divide 6,9 but 6 doesnt divide 9 evenly--> 6,9
5 divide 10-->> 10 important
that leaves us with 6,7,8,9,10 to check if there is any number from 1 that can divide this with rest 0.
the trick is if 2,4,8 divides a number let say 16 with the same reminder then we don't have to check if 2,4 divides 16, we check only 8.
11 12 13 14 15 16 17 18 19 20
here we can do the same from about with factors of the numbers from above and we will be left with
11 12 13 14 15 16 17 18 19 20
NB: we know that the last number that has to divide the number is 20,
so that means either the solution will be a number ending with 0 or is
one of the factors of 20, so we build factors of 20 and check if 11 12
13 14 15 16 17 18 19 can divide it then we are done.
int start = 20;
while (start % 11 != 0 || start % 12 != 0 | start % 13 != 0 || start % 14 != 0 ||
start % 15 != 0 || start % 16 != 0 || start % 17 != 0 || start % 18 != 0 || start % 19 != 0 )
{
start += 20;
}
console.log(start)
The same idea applies analogue to the first deduction I made to make the
problem seems smaller.
//smallest number divisible by all numbers from 1 to 10
int a = 10;
while (a % 6 != 0 || a % 7 != 0 | a % 8 != 0 || a % 9 != 0 )
{
a += 10;
}
console.log(a)
//smallest number divisible by all numbers from 1 to 5
int i = 5;
while (i % 3 != 0 || i % 4 != 0)
{
i += 5;
}
console.log(i)
After watching this video
http://youtu.be/3QnD2c4Xovk
I've been trying to follow it step by step, and haven't been able to produce the same results.
Notably, when I try to do Math.pow(3, 54)%17, I get 7. While the speaker gets 15.
I wrote a method that is supposed to simulate Diffie Hellman's key exchange using exactly what I found on http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
This is my code:
function diffieHellman(generator, prime, alice_secret, bob_secret){
var alice_public = Math.pow(generator, alice_secret)%prime
, bob_public = Math.pow(generator, bob_secret)%prime
, alice_private = Math.pow(bob_public, alice_secret)%prime
, bob_private = Math.pow(alice_public, bob_secret)%prime;
console.log("alice"
, "\n\t", "secret -- ", alice_secret
, "\n\t", "public -- ", alice_public
, "\n\t", "private -- ", alice_private
)
console.log("bob"
, "\n\t", "secret -- ", bob_secret
, "\n\t", "public -- ", bob_public
, "\n\t", "private -- ", bob_private
)
return {
alice:{
secret: alice_secret
, public: alice_public
, private: alice_private
},
bob:{
secret: bob_secret
, public: bob_public
, private: bob_private
}
}
};
These examples work:
diffieHellman(3, 17, 4, 12) // 1, 1
diffieHellman(3, 23, 6, 19) // 12, 12
diffieHellman(3, 13, 8, 4) // 9, 9
However, some numbers don't work
diffieHellman(3, 17, 40, 120) // 13, 0
diffieHellman(3, 23, 16, 129) // 21, 2
diffieHellman(3, 13, 44, 11) // 9, 1
What am I doing wrong?
Edit -- I'm not trying to implement Diffie-Hellman's Key Exchange in Javascript for a project. It's just the language I'm most comfortable with, but I am afraid if this could be a javascript limitation.
The problem is the limited precision of Javascript numbers that causes rounding errors in your code where you first exponentiate and then calculate the modulus. For your example numbers, you could fix this by periodically calculating the modulus inside the exponentiation, e.g. by never calculating more than a square before taking the modulus. But for actual cryptography your calculations will involve numbers too big to be handled as Javascript numbers (and most programming languages pose the same problem). The usual approach is to use a large integer (or even arbitrary precision) library. If you do end up implementing your own cryptography, please watch out for side channels, e.g. by calling library functions that are not constant time or allow cache-based attacks by using data dependent array indices.
3^54 is 58149737003040059690390169. It causes an overflow, therefore you should implement modular exponentation, since i don't know javascript too well i have written a c code which should be easy to implement in javascript :
int power(int a, int b, int prime){
int result;
if(b == 0){
result = 1;
}else if(b == 1){
result = a % prime;
}else if(b % 2 == 0){
result = power((a*a) % prime, b/2, prime);
result = result % prime;
}else{
result = power((a*a) % prime, b/2, prime);
result = (result * a) % prime;
}
return result;
}
Now you can call this function :
int value = power(3, 54, 17);
and it should work.
Edit: added javascript version
function power(a, b, prime) {
if (b <= 0) {
return 1;
} else if (b === 1) {
return a % prime;
} else if (b % 2 === 0) {
return power((a * a) % prime, b / 2 | 0, prime) % prime;
} else {
return (power((a * a) % prime, b / 2 | 0, prime) * a) % prime;
}
}