That's because it would take a very long time, and at some point you would have so many permutations that they wouldn't fit in an array.
Table of contents
Starting point
By "array permutations" I'm referring to my blog post from yesterday: Generating array permutations with all lengths in JS.
Here's the code:
const getPermutations = (items) =>
items.reduce((permutations, item) => {
const newPermutations = permutations.map((permutation) =>
permutation.concat(item)
)
permutations.push([item], ...newPermutations)
return permutations
}, [])
getPermutations([1, 2, 3])
//=> [[1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
RangeErrors
The code throws if the input array's length is too long. In Firefox the limit is 20:
getPermutations(Array.from({ length: 19 })) // OK
getPermutations(Array.from({ length: 20 })) // RangeError
- In Firefox, the error message is "too many function arguments."
- In Chrome, Edge, Safari and Node.js, the error message is "Maximum call stack size exceeded."
Firefox's error message seems more helpful
since the problem is that permutations.push()
is called with too many arguments.
In Firefox the limit is 500,000 items:
;[].push(...Array(500_000)) // OK
;[].push(...Array(500_001)) // RangeError
Avoiding RangeErrors
We could try to avoid the problem by avoiding the spread syntax altogether:
const getPermutations = (items) =>
items.reduce((permutations, item) => {
- const newPermutations = permutations.map((permutation) =>
- permutation.concat(item)
- )
- permutations.push([item], ...newPermutations)
+ permutations.forEach((permutation) =>
+ permutations.push(permutation.concat(item))
+ )
+ permutations.push([item])
return permutations
}, [])
(I thought that this would also increase performance,
but it doesn't seem to.
Makes sense though:
we do avoid iterating through newPermutations
which is good,
but now we are calling permutations.push()
many more times with only one argument.)
It's slow
Now larger input arrays work, but getting permutations for large input arrays is increasingly slow:
console.time()
getPermutations(Array.from({ length: 20 }))
console.timeEnd() //=> ~200ms (on my machine)
console.time()
getPermutations(Array.from({ length: 21 }))
console.timeEnd() //=> ~400ms (on my machine)
console.time()
getPermutations(Array.from({ length: 22 }))
console.timeEnd() //=> ~800ms (on my machine)
console.time()
getPermutations(Array.from({ length: 23 }))
console.timeEnd() //=> ~1800ms (on my machine)
console.time()
getPermutations(Array.from({ length: 24 }))
console.timeEnd() //=> ~4200ms (on my machine)
Ain't nobody got time to wait that long!
The input array's length is still limited
In Firefox,
the maximum length of an array seems to be 2 ** 32 - 1
:
Array(2 ** 32 - 1) // OK
Array(2 ** 32) // RangeError: invalid array length
The length of the array returned by getPermutations(items)
is incidentally 2 ** items.length - 1
:
getPermutations(Array.from({ length: 1 })).length === 2 ** 1 - 1 // true
getPermutations(Array.from({ length: 2 })).length === 2 ** 2 - 1 // true
getPermutations(Array.from({ length: 3 })).length === 2 ** 3 - 1 // true
// ...
getPermutations(Array.from({ length: 10 })).length === 2 ** 10 - 1 // true
getPermutations(Array.from({ length: 11 })).length === 2 ** 11 - 1 // true
getPermutations(Array.from({ length: 12 })).length === 2 ** 12 - 1 // true
// ...and so on
(Math for calculating the total number of permutations.)
In other words, the maximum length of the input array is 32.
But input arrays that long would be impractical
since getting all permutations would take way too long.
Plus there would be quite many permutations:
2 ** 32 - 1
= 4,294,967,295.
Over 4 billion!
Think of alternatives
Okay, so the input array's length is limited to 32 (in Firefox at least), and getting all permutations would take a very long time, so think:
- Do you need to generate all permutations?
- Do you need to retain all permutations?
Some pointers:
- If you only need permutations that are e.g. 2 or 3 items long, there's no point in generating longer permutations.
- If you are looking for a single permutation that matches a certain criterion,
terminate the process after finding your solution.
- Not possible if using an array reducer;
use
for
loops and thebreak
statement instead.
- Not possible if using an array reducer;
use
- If you are looking for multiple permutations that match a certain criterion, skip non-matching permutations early, i.e. don't store them in the accumulator array.
- I don't remember if I have ever used generator functions, but this could be a good use case for them. I got this idea from Evan Hahn's blog post Nested array permutations in JavaScript.