Related
This question already has answers here:
Split array into chunks
(73 answers)
Closed 10 months ago.
Problem: Write a function that splits an array (first argument) into groups the length of size (second argument) and returns them as a two-dimensional array.
Why does my test2 variable not working?
function chunkArrayInGroups(arr, size) {
let resArr = [];
for (let i = 0; i < arr.length; i++) {
resArr.push(arr.splice(0, size));
}
return resArr;
}
let test = chunkArrayInGroups(["a", "b", "c", "d"], 2);
console.log(test);
// returns correct [["a", "b"], ["c", "d"]]
let test2 = chunkArrayInGroups([0, 1, 2, 3, 4, 5], 2);
console.log(test2);
// should return [[0, 1], [2, 3], [4, 5]]
//but returns [[0, 1], [2, 3]]
Why?
Thank you!
arr.length changing on every iteration. And with incrementing i does not full fill condition.
Try below snippet
function chunkArrayInGroups(arr, size) {
let resArr = [];
while (arr.length) {
resArr.push(arr.splice(0, size));
}
return resArr;
}
let test = chunkArrayInGroups(["a", "b", "c", "d"], 2);
console.log(test);
let test2 = chunkArrayInGroups([0, 1, 2, 3, 4, 5], 2);
console.log(test2);
You're using an index i which moves forward by one element, but meanwhile you're removing two per cycle, so the index falls beyond the array length sooner than you expect.
Instead of using an indexed for, just use a while condition that checks whether your array is empty or not. If it's not empty, countinue splice-ing:
function chunkArrayInGroups(arr, size) {
let resArr = [];
while (arr.length > 0) {
resArr.push(arr.splice(0, size));
}
return resArr;
}
let test = chunkArrayInGroups(["a", "b", "c", "d"], 2);
console.log(test);
let test2 = chunkArrayInGroups([0, 1, 2, 3, 4, 5], 2);
console.log(test2);
function chunkArrayInGroups(arr, size) {
let resArr = [];
for(i = 0; i < arr.length; i += size){
resArr.push(arr.slice(i, i + size))
}
return resArr;
}
let test = chunkArrayInGroups(["f", "b", "c", "d", "d", "b"], 2);
console.log(test);
// returns correct [["a", "b"], ["c", "d"]]
let test2 = chunkArrayInGroups([0, 1, 2, 3, 4, 5], 3);
console.log(test2);
So this would be my method it uses slice instead of splice but this works like a charm.
Since we're using splice, we're modifying the length of the original array, which means we shouldn't rely on it to loop through. Instead, we should loop through a range equalling to the length of the array we want to return, which can be calculated by just dividing inputArr.length / size.
You can create a "range" and loop through it with a for..of loop by using Array(number).keys()
const caseOne = ['a', 'b', 'c', 'd'];
const caseTwo = [0, 1, 2, 3, 4, 5];
const caseThree = [1, 'hi', 3, 9, 'a', { hello: 'world' }, 7563, 'c', 3, [1, 2, 3]];
const chunkArray = (arr, num) => {
// The final array we'll push to
const final = [];
// Loop through the future length of the "final" array
for (const _ of Array(Math.ceil(arr.length / num)).keys()) {
final.push(arr.splice(0, num));
}
return final;
};
console.log(chunkArray(caseOne, 2));
console.log(chunkArray(caseTwo, 2));
console.log(chunkArray(caseThree, 3));
You could also use the reduce method:
const caseOne = ['a', 'b', 'c', 'd'];
const caseTwo = [0, 1, 2, 3, 4, 5];
const caseThree = [1, 'hi', 3, 9, 'a', { hello: 'world' }, 7563, 'c', 3, [1, 2, 3]];
const chunkArray = (arr, num) => {
return [...Array(Math.ceil(arr.length / num)).keys()].reduce((acc) => {
acc.push(arr.splice(0, num));
return acc;
}, []);
};
console.log(chunkArray(caseOne, 2));
console.log(chunkArray(caseTwo, 2));
console.log(chunkArray(caseThree, 3));
Not only does arr.length change with every iteration, but the incrementor should be the size variable, rather than +1
function chunkArrayInGroups(arr, size) {
let resArr = [], l = arr.length;
for (let i = 0; i < l; i+=size) {
resArr.push(arr.splice(0, size));
}
return resArr;
}
let test = chunkArrayInGroups(["a", "b", "c", "d"], 2);
console.log(test);
// returns correct [["a", "b"], ["c", "d"]]
let test2 = chunkArrayInGroups([0, 1, 2, 3, 4, 5], 2);
console.log(test2);
// should return [[0, 1], [2, 3], [4, 5]]
//but returns [[0, 1], [2, 3]]
If you prefer a more functional approach
const chunkArrayInGroups = (arr, size) =>
Array(Math.ceil(parseFloat(arr.length) / size))
.fill(0)
.map((_, i)=> arr.slice(i * size, (i + 1) *size))
let test = chunkArrayInGroups(["a", "b", "c", "d"], 2);
console.log(test);
let test2 = chunkArrayInGroups([0, 1, 2, 3, 4, 5], 2);
console.log(test2);
I was speaking with a coworker today who had just given a whiteboard exam to a potential employee (who did not get an offer) and it made me wonder if I could solve this problem given to an entry level prospect.
Well I couldn't.
The problem is this... Merge two sorted arrays into a single array.
arrayOne = ['a', 'b', 'c', ...'z']
arrayTwo = [1, 2, 3, 4, 5, ...100]
result = [1, 'a', 2, 'b', ...26, 'z', 27, 28, ...100]
The idea here is that
a = 1
b = 2
c = 3
...
z = 26
I've looked around and can't find a simple solution to this. Keep in mind we're primarily JavaScript, but the potential employee can use any language they choose in the interview.
My sad excuse of an attempt:
function merge_arrays(arr1, arr2) {
let result = [];
let i1 = 0;
let i2 = 0;
for (var i = 0; i < arr1.length + arr2.length; i++) {
if (arr1[i1] > arr2[i2]) {
result.push(arr2[i2]);
i2 += 1;
} else {
result.push(arr1[i1]);
i1 += 1;
}
}
return result;
}
array1 = ["a", "b", "c", "d", "e"];
array2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
// returns ["a", "b", "c", "d", "e", undefined, undefined, ...undefined]
Here's a scenario where you can't just take the next index and assume it's in the correct spot.
array1 = ["a", "c", "d", "e"];
array2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
// should return ["a", 1, 2, "c", 3, "d", 4, ...13]
A solution in TypeScript is:
var arrayOne: number[] = [];
for (var i = 1; i<=100; i++) arrayOne.push(i);
var arrayTwo: string[] = ['a', 'b', 'c', 'd', 'e'];
var max = Math.max(arrayOne.length, arrayTwo.length);
var result = [];
for (var i = 0; i < max; i++){
if (arrayTwo[i])
result.push(arrayTwo[i]);
if (arrayOne[i])
result.push(arrayOne[i]);
}
console.log(result)
> [1, 'a', 2, 'b', 3, 'c', 4, 'd', 5, 'e', 6, ..., 26, 27, 28, ..., 100]
one can add an if to check which should be added first, but seemed verbose to add since its not specified.
Something like this should work, it just iterates both arrays and only adds elements to the new "merged" array if they exist (might want to check for null or undefined if 0 or '' can be present).
function merge(a1, a2) {
const merged = Array(a1.length + a2.length);
let index = 0, i1 = 0, i2 = 0;
while (i1 < a1.length || i2 < a2.length) {
if (a1[i1] && a2[i2]) {
const item1 = a1[i1];
const item2 = a2[i2].charCodeAt(0)-96;
merged[index++] = (item1 < item2) ? a1[i1++] : a2[i2++]
}
else if (a1[i1]) merged[index++] = a1[i1++];
else if (a2[i2]) merged[index++] = a2[i2++];
}
return merged;
}
// Array with characters always passed as 2nd array
const array1 = [1, 2, 3, 4, 5, 6];
const array2 = ['a', 'c', 'd', 'e'];
const result = merge(array1, array2);
console.log(result)
Just do a concat on array like this
let newArray = [].concat(arr1,arr2)
arr1 and arr2 can have different types.
I need to check three different arrays to see if their indexes match up. If they match an object is created. The third array may have less items in the array. If the third array has less items, then the previous two arrays should continue checking their indexes for a match and create a different object. The array indexes that match the ones in the "seqIds" array should add "seqId" as a property, the indexes in the other two arrays that don't match with a "seqId" index don't get the "seqId" property.
Edit: The invIds and invTypes arrays will always be the same length.
Example arrays:
invIds: [1, 2, 3, 4];
invTypes: ["A", "B", "C", "D"];
seqIds: [10, 11];
The invs array should consist of these objects:
invs: [
{
"invId": 1,
"invType": "A",
"seqId": 10
},
{
"invId": 2,
"invType": "B",
"seqId": 11
},
{
"invId": 3,
"invType": "C"
},
{
"invId": 4,
"invType": "D"
}
];
The for loop I wrote:
var invs = [];
for (var invI = 0; invI < this.state.invIds.length; invI++) {
for (var invT = 0; invT < this.state.invTypes.length; invT++) {
for (var invS = 0; invS < this.state.invSeqIds.length; invS++) {
if (invI === invT && invT === invS) {
invs.push({
seqId: this.state.invSeqIds[invS],
userId: this.state.invIds[invI],
invTypeCd: this.state.invTypes[invT],
importId: randInt
});
}
}
if (invI === invT) {
invs.push({
userId: this.state.invIds[invI],
invTypeCd: this.state.invTypes[invT],
importId: randInt
});
}
}
}
The for loop I wrote is not adding into the array properly, it does this:
{"invId": 1, "invType": "A", "seqId": 10}
{"invId": 1, "invType": "A"}
{"invId": 2, "invType": "B", "seqId": 11}
{"invId": 2, "invType": "B"}
{"invId": 3, "invType": "C"}
{"invId": 4, "invType": "D"}
An alternative to solve this is by using a while-statement along with the operator in to check for the index of the source array.
let invIds = [1, 2, 3, 4],
invTypes = ["A", "B", "C", "D"],
seqIds = [10, 11],
result = [],
i = 0;
while (i in invIds && i in invTypes) {
result[i] = Object.create(null);
result[i].invId = invIds[i];
result[i].invType = invTypes[i];
if (i in seqIds) result[i].seqId = seqIds[i];
i++;
}
console.log(result);
.as-console-wrapper {max-height: 100% !important;top: 0;}
var invIds = [1, 2, 3, 4];
var invTypes = ["A", "B", "C", "D"];
var seqIds = [10, 11];
var invs = [];
for (var i = 0, length = invIds.length; i < length; i++) {
var inv = {
invId: invIds[i],
invType: invTypes[i]
};
if (i < seqIds.length) {
inv.seqId = seqIds[i];
}
invs.push(inv)
}
console.log(invs);
Or js ES6 way
const invIds = [1, 2, 3, 4];
const invTypes = ["A", "B", "C", "D"];
const seqIds = [10, 11];
const invs = Array(invIds.length)
.fill(undefined).map((_, i) => {
const result = {
invId: invIds[i],
invType: invTypes[i]
};
if (seqIds.length > i) result.seqId = seqIds[i];
return result;
});
console.log(invs)
this is want you want right? Just use a temporary variable to store the object property and push to the invs.
if seqIds has less number of elements check before assigning tmp a property of seqIds that it exists or not.
let invIds = [1, 2, 3, 4], invTypes = ["A", "B", "C", "D"], seqIds = [10, 11];
var invs = [];
for (let i = 0; i< invIds.length; ++i) {
let tmp = {};
tmp.invIds = invIds[i];
tmp.invTypes = invTypes[i];
if(seqIds[i]) tmp.seqIds = seqIds[i];
invs.push(tmp);
}
console.log(invs);
Try this:
var invs = this.state.invIds.map(function(item, index){
var objReturned ={}
objReturned.invId = item;
if(this.state.invTypes[index]) objReturned.invType = this.state.invTypes[index]
if(this.state.invSeqIds[index]) objReturned.seqId = this.state.invSeqIds[index]
return objReturned;
} )
I would not recommend using nested loops as that can get quite messy, and it is very difficult to understand and maintain. I would break it down into the following steps:
Define a result array where you will store your results, and find the largest data set
let invIds = [1, 2, 3, 4];
let invTypes = ["A", "B", "C", "D"];
let seqIds = [10, 11];
let result = [];
let maxLength = Math.max(invIds.length, invTypes.length, seqIds.length);
Iterate over all the indices starting from 0 to the end of the largest data set, and add a new object to the result array if it meets your conditions
for (let i = 0; i < maxLength; i++) {
let newItem = {};
// If the property exists and it meets your conditions
// Also keep in mind if any id is 0, you will have to add to the
// condition as 0 is a falsy value
if (invIds[i] || invIds[i] === 0) {
newItem.invId = invIds[i];
}
if (invTypes[i]) {
newItem.invType = invTypes[i];
}
if (seqIds[i] || seqIds[i] === 0) {
newItem.seqId = seqIds[i];
}
result.push(newItem);
}
I have two arrays:
var array1 = ["A", "B", "C"];
var array2 = ["1", "2", "3"];
How can I set another array to contain every combination of the above, so that:
var combos = ["A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3"];
Or if you'd like to create combinations with an arbitrary number of arrays of arbitrary sizes...(I'm sure you can do this recursively, but since this isn't a job interview, I'm instead using an iterative "odometer" for this...it increments a "number" with each digit a "base-n" digit based on the length of each array)...for example...
combineArrays([ ["A","B","C"],
["+", "-", "*", "/"],
["1","2"] ] )
...returns...
[
"A+1","A+2","A-1", "A-2",
"A*1", "A*2", "A/1", "A/2",
"B+1","B+2","B-1", "B-2",
"B*1", "B*2", "B/1", "B/2",
"C+1","C+2","C-1", "C-2",
"C*1", "C*2", "C/1", "C/2"
]
...each of these corresponding to an "odometer" value that
picks an index from each array...
[0,0,0], [0,0,1], [0,1,0], [0,1,1]
[0,2,0], [0,2,1], [0,3,0], [0,3,1]
[1,0,0], [1,0,1], [1,1,0], [1,1,1]
[1,2,0], [1,2,1], [1,3,0], [1,3,1]
[2,0,0], [2,0,1], [2,1,0], [2,1,1]
[2,2,0], [2,2,1], [2,3,0], [2,3,1]
The "odometer" method allows you to easily generate
the type of output you want, not just the concatenated strings
like we have here. Besides that, by avoiding recursion
we avoid the possibility of -- dare I say it? -- a stack overflow...
function combineArrays( array_of_arrays ){
// First, handle some degenerate cases...
if( ! array_of_arrays ){
// Or maybe we should toss an exception...?
return [];
}
if( ! Array.isArray( array_of_arrays ) ){
// Or maybe we should toss an exception...?
return [];
}
if( array_of_arrays.length == 0 ){
return [];
}
for( let i = 0 ; i < array_of_arrays.length; i++ ){
if( ! Array.isArray(array_of_arrays[i]) || array_of_arrays[i].length == 0 ){
// If any of the arrays in array_of_arrays are not arrays or zero-length, return an empty array...
return [];
}
}
// Done with degenerate cases...
// Start "odometer" with a 0 for each array in array_of_arrays.
let odometer = new Array( array_of_arrays.length );
odometer.fill( 0 );
let output = [];
let newCombination = formCombination( odometer, array_of_arrays );
output.push( newCombination );
while ( odometer_increment( odometer, array_of_arrays ) ){
newCombination = formCombination( odometer, array_of_arrays );
output.push( newCombination );
}
return output;
}/* combineArrays() */
// Translate "odometer" to combinations from array_of_arrays
function formCombination( odometer, array_of_arrays ){
// In Imperative Programmingese (i.e., English):
// let s_output = "";
// for( let i=0; i < odometer.length; i++ ){
// s_output += "" + array_of_arrays[i][odometer[i]];
// }
// return s_output;
// In Functional Programmingese (Henny Youngman one-liner):
return odometer.reduce(
function(accumulator, odometer_value, odometer_index){
return "" + accumulator + array_of_arrays[odometer_index][odometer_value];
},
""
);
}/* formCombination() */
function odometer_increment( odometer, array_of_arrays ){
// Basically, work you way from the rightmost digit of the "odometer"...
// if you're able to increment without cycling that digit back to zero,
// you're all done, otherwise, cycle that digit to zero and go one digit to the
// left, and begin again until you're able to increment a digit
// without cycling it...simple, huh...?
for( let i_odometer_digit = odometer.length-1; i_odometer_digit >=0; i_odometer_digit-- ){
let maxee = array_of_arrays[i_odometer_digit].length - 1;
if( odometer[i_odometer_digit] + 1 <= maxee ){
// increment, and you're done...
odometer[i_odometer_digit]++;
return true;
}
else{
if( i_odometer_digit - 1 < 0 ){
// No more digits left to increment, end of the line...
return false;
}
else{
// Can't increment this digit, cycle it to zero and continue
// the loop to go over to the next digit...
odometer[i_odometer_digit]=0;
continue;
}
}
}/* for( let odometer_digit = odometer.length-1; odometer_digit >=0; odometer_digit-- ) */
}/* odometer_increment() */
Just in case anyone is looking for Array.map solution
var array1=["A","B","C"];
var array2=["1","2","3","4"];
console.log(array1.flatMap(d => array2.map(v => d + v)))
Seeing a lot of for loops in all of the answers...
Here's a recursive solution I came up with that will find all combinations of N number of arrays by taking 1 element from each array:
const array1=["A","B","C"]
const array2=["1","2","3"]
const array3=["red","blue","green"]
const combine = ([head, ...[headTail, ...tailTail]]) => {
if (!headTail) return head
const combined = headTail.reduce((acc, x) => {
return acc.concat(head.map(h => `${h}${x}`))
}, [])
return combine([combined, ...tailTail])
}
console.log('With your example arrays:', combine([array1, array2]))
console.log('With N arrays:', combine([array1, array2, array3]))
//-----------UPDATE BELOW FOR COMMENT---------
// With objects
const array4=[{letter: "A"}, {letter: "B"}, {letter: "C"}]
const array5=[{number: 1}, {number: 2}, {number: 3}]
const array6=[{color: "RED"}, {color: "BLUE"}, {color: "GREEN"}]
const combineObjects = ([head, ...[headTail, ...tailTail]]) => {
if (!headTail) return head
const combined = headTail.reduce((acc, x) => {
return acc.concat(head.map(h => ({...h, ...x})))
}, [])
return combineObjects([combined, ...tailTail])
}
console.log('With arrays of objects:', combineObjects([array4, array5, array6]))
A loop of this form
combos = [] //or combos = new Array(2);
for(var i = 0; i < array1.length; i++)
{
for(var j = 0; j < array2.length; j++)
{
//you would access the element of the array as array1[i] and array2[j]
//create and array with as many elements as the number of arrays you are to combine
//add them in
//you could have as many dimensions as you need
combos.push(array1[i] + array2[j])
}
}
Assuming you're using a recent web browser with support for Array.forEach:
var combos = [];
array1.forEach(function(a1){
array2.forEach(function(a2){
combos.push(a1 + a2);
});
});
If you don't have forEach, it is an easy enough exercise to rewrite this without it. As others have proven before, there's also some performance advantages to doing without... (Though I contend that not long from now, the common JavaScript runtimes will optimize away any current advantages to doing this otherwise.)
Solution enhancement for #Nitish Narang's answer.
Use reduce in combo with flatMap to support N arrays combination.
const combo = [
["A", "B", "C"],
["1", "2", "3", "4"]
];
console.log(combo.reduce((a, b) => a.flatMap(x => b.map(y => x + y)), ['']))
Here is functional programming ES6 solution:
var array1=["A","B","C"];
var array2=["1","2","3"];
var result = array1.reduce( (a, v) =>
[...a, ...array2.map(x=>v+x)],
[]);
/*---------OR--------------*/
var result1 = array1.reduce( (a, v, i) =>
a.concat(array2.map( w => v + w )),
[]);
/*-------------OR(without arrow function)---------------*/
var result2 = array1.reduce(function(a, v, i) {
a = a.concat(array2.map(function(w){
return v + w
}));
return a;
},[]
);
console.log(result);
console.log(result1);
console.log(result2)
Part II: After my complicated iterative "odometer" solution of July 2018, here's a simpler recursive version of combineArraysRecursively()...
function combineArraysRecursively( array_of_arrays ){
// First, handle some degenerate cases...
if( ! array_of_arrays ){
// Or maybe we should toss an exception...?
return [];
}
if( ! Array.isArray( array_of_arrays ) ){
// Or maybe we should toss an exception...?
return [];
}
if( array_of_arrays.length == 0 ){
return [];
}
for( let i = 0 ; i < array_of_arrays.length; i++ ){
if( ! Array.isArray(array_of_arrays[i]) || array_of_arrays[i].length == 0 ){
// If any of the arrays in array_of_arrays are not arrays or are zero-length array, return an empty array...
return [];
}
}
// Done with degenerate cases...
let outputs = [];
function permute(arrayOfArrays, whichArray=0, output=""){
arrayOfArrays[whichArray].forEach((array_element)=>{
if( whichArray == array_of_arrays.length - 1 ){
// Base case...
outputs.push( output + array_element );
}
else{
// Recursive case...
permute(arrayOfArrays, whichArray+1, output + array_element );
}
});/* forEach() */
}
permute(array_of_arrays);
return outputs;
}/* function combineArraysRecursively() */
const array1 = ["A","B","C"];
const array2 = ["+", "-", "*", "/"];
const array3 = ["1","2"];
console.log("combineArraysRecursively(array1, array2, array3) = ", combineArraysRecursively([array1, array2, array3]) );
Here is another take. Just one function and no recursion.
function allCombinations(arrays) {
const numberOfCombinations = arrays.reduce(
(res, array) => res * array.length,
1
)
const result = Array(numberOfCombinations)
.fill(0)
.map(() => [])
let repeatEachElement
for (let i = 0; i < arrays.length; i++) {
const array = arrays[i]
repeatEachElement = repeatEachElement ?
repeatEachElement / array.length :
numberOfCombinations / array.length
const everyElementRepeatedLength = repeatEachElement * array.length
for (let j = 0; j < numberOfCombinations; j++) {
const index = Math.floor(
(j % everyElementRepeatedLength) / repeatEachElement
)
result[j][i] = array[index]
}
}
return result
}
const result = allCombinations([
['a', 'b', 'c', 'd'],
[1, 2, 3],
[true, false],
])
console.log(result.join('\n'))
Arbitrary number of arrays, arbitrary number of elements.
Sort of using number base theory I guess - the j-th array changes to the next element every time the number of combinations of the j-1 arrays has been exhausted. Calling these arrays 'vectors' here.
let vectorsInstance = [
[1, 2],
[6, 7, 9],
[10, 11],
[1, 5, 8, 17]]
function getCombos(vectors) {
function countComb(vectors) {
let numComb = 1
for (vector of vectors) {
numComb *= vector.length
}
return numComb
}
let allComb = countComb(vectors)
let combos = []
for (let i = 0; i < allComb; i++) {
let thisCombo = []
for (j = 0; j < vectors.length; j++) {
let vector = vectors[j]
let prevComb = countComb(vectors.slice(0, j))
thisCombo.push(vector[Math.floor(i / prevComb) % vector.length])
}
combos.push(thisCombo)
}
return combos
}
console.log(getCombos(vectorsInstance))
While there's already plenty of good answers to get every combination, which is of course the original question, I'd just like to add a solution for pagination. Whenever there's permutations involved, there's the risk of extremely large numbers. Let's say, for whatever reason, we wanted to build an interface where a user could still browse through pages of practically unlimited permutations, e.g. show permutations 750-760 out of one gazillion.
We could do so using an odometer similar to the one in John's solution. Instead of only incrementing our way through the odometer, we also calculate its initial value, similar to how you'd convert for example seconds into a hh:mm:ss clock.
function getPermutations(arrays, startIndex = 0, endIndex) {
if (
!Array.isArray(arrays) ||
arrays.length === 0 ||
arrays.some(array => !Array.isArray(array))
) {
return { start: 0, end: 0, total: 0, permutations: [] };
}
const permutations = [];
const arrayCount = arrays.length;
const arrayLengths = arrays.map(a => a.length);
const maxIndex = arrayLengths.reduce(
(product, arrayLength) => product * arrayLength,
1,
);
if (typeof endIndex !== 'number' || endIndex > maxIndex) {
endIndex = maxIndex;
}
const odometer = Array.from({ length: arrayCount }).fill(0);
for (let i = startIndex; i < endIndex; i++) {
let _i = i; // _i is modified and assigned to odometer indexes
for (let odometerIndex = arrayCount - 1; odometerIndex >= 0; odometerIndex--) {
odometer[odometerIndex] = _i % arrayLengths[odometerIndex];
if (odometer[odometerIndex] > 0 && i > startIndex) {
// Higher order values in existing odometer are still valid
// if we're not hitting 0, since there's been no overflow.
// However, startIndex always needs to follow through the loop
// to assign initial odometer.
break;
}
// Prepare _i for next odometer index by truncating rightmost digit
_i = Math.floor(_i / arrayLengths[odometerIndex]);
}
permutations.push(
odometer.map(
(odometerValue, odometerIndex) => arrays[odometerIndex][odometerValue],
),
);
}
return {
start: startIndex,
end: endIndex,
total: maxIndex,
permutations,
};
}
So for the original question, we'd do
getPermutations([['A', 'B', 'C'], ['1', '2', '3']]);
-->
{
"start": 0,
"end": 9,
"total": 9,
"permutations": [
["A", "1"],
["A", "2"],
["A", "3"],
["B", "1"],
["B", "2"],
["B", "3"],
["C", "1"],
["C", "2"],
["C", "3"]
]
}
but we could also do
getPermutations([['A', 'B', 'C'], ['1', '2', '3']], 2, 5);
-->
{
"start": 2,
"end": 5,
"total": 9,
"permutations": [
["A", "3"],
["B", "1"],
["B", "2"]
]
}
And more importantly, we could do
getPermutations(
[
new Array(1000).fill(0),
new Array(1000).fill(1),
new Array(1000).fill(2),
new Array(1000).fill(3),
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'],
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
['X', 'Y', 'Z'],
['1', '2', '3', '4', '5', '6']
],
750,
760
);
-->
{
"start": 750,
"end": 760,
"total": 1800000000000000,
"permutations": [
[0, 1, 2, 3, "e", "B", "Z", "1"],
[0, 1, 2, 3, "e", "B", "Z", "2"],
[0, 1, 2, 3, "e", "B", "Z", "3"],
[0, 1, 2, 3, "e", "B", "Z", "4"],
[0, 1, 2, 3, "e", "B", "Z", "5"],
[0, 1, 2, 3, "e", "B", "Z", "6"],
[0, 1, 2, 3, "e", "C", "X", "1"],
[0, 1, 2, 3, "e", "C", "X", "2"],
[0, 1, 2, 3, "e", "C", "X", "3"],
[0, 1, 2, 3, "e", "C", "X", "4"]
]
}
without the computer hanging.
Here's a short recursive one that takes N arrays.
function permuteArrays(first, next, ...rest) {
if (rest.length) next = permuteArrays(next, ...rest);
return first.flatMap(a => next.map(b => [a, b].flat()));
}
Or with reduce (slight enhancement of Penny Liu's):
function multiply(a, b) {
return a.flatMap(c => b.map(d => [c, d].flat()));
}
[['a', 'b', 'c'], ['+', '-'], [1, 2, 3]].reduce(multiply);
Runnable example:
function permuteArrays(first, next, ...rest) {
if (rest.length) next = permuteArrays(next, ...rest);
return first.flatMap(a => next.map(b => [a, b].flat()));
}
const squish = arr => arr.join('');
console.log(
permuteArrays(['A', 'B', 'C'], ['+', '-', '×', '÷'], [1, 2]).map(squish),
permuteArrays(['a', 'b', 'c'], [1, 2, 3]).map(squish),
permuteArrays([['a', 'foo'], 'b'], [1, 2]).map(squish),
permuteArrays(['a', 'b', 'c'], [1, 2, 3], ['foo', 'bar', 'baz']).map(squish),
)
I had a similar requirement, but I needed get all combinations of the keys of an object so that I could split it into multiple objects. For example, I needed to convert the following;
{ key1: [value1, value2], key2: [value3, value4] }
into the following 4 objects
{ key1: value1, key2: value3 }
{ key1: value1, key2: value4 }
{ key1: value2, key2: value3 }
{ key1: value2, key2: value4 }
I solved this with an entry function splitToMultipleKeys and a recursive function spreadKeys;
function spreadKeys(master, objects) {
const masterKeys = Object.keys(master);
const nextKey = masterKeys.pop();
const nextValue = master[nextKey];
const newObjects = [];
for (const value of nextValue) {
for (const ob of objects) {
const newObject = Object.assign({ [nextKey]: value }, ob);
newObjects.push(newObject);
}
}
if (masterKeys.length === 0) {
return newObjects;
}
const masterClone = Object.assign({}, master);
delete masterClone[nextKey];
return spreadKeys(masterClone, newObjects);
}
export function splitToMultipleKeys(key) {
const objects = [{}];
return spreadKeys(key, objects);
}
one more:
const buildCombinations = (allGroups: string[][]) => {
const indexInArray = new Array(allGroups.length);
indexInArray.fill(0);
let arrayIndex = 0;
const resultArray: string[] = [];
while (allGroups[arrayIndex]) {
let str = "";
allGroups.forEach((g, index) => {
str += g[indexInArray[index]];
});
resultArray.push(str);
// if not last item in array already, switch index to next item in array
if (indexInArray[arrayIndex] < allGroups[arrayIndex].length - 1) {
indexInArray[arrayIndex] += 1;
} else {
// set item index for the next array
indexInArray[arrayIndex] = 0;
arrayIndex += 1;
// exclude arrays with 1 element
while (allGroups[arrayIndex] && allGroups[arrayIndex].length === 1) {
arrayIndex += 1;
}
indexInArray[arrayIndex] = 1;
}
}
return resultArray;
};
One example:
const testArrays = [["a","b"],["c"],["d","e","f"]]
const result = buildCombinations(testArrays)
// -> ["acd","bcd","ace","acf"]
My version of the solution by John D. Aynedjian, which I rewrote for my own understanding.
console.log(getPermutations([["A","B","C"],["1","2","3"]]));
function getPermutations(arrayOfArrays)
{
let permutations=[];
let remainder,permutation;
let permutationCount=1;
let placeValue=1;
let placeValues=new Array(arrayOfArrays.length);
for(let i=arrayOfArrays.length-1;i>=0;i--)
{
placeValues[i]=placeValue;
placeValue*=arrayOfArrays[i].length;
}
permutationCount=placeValue;
for(let i=0;i<permutationCount;i++)
{
remainder=i;
permutation=[];
for(let j=0;j<arrayOfArrays.length;j++)
{
permutation[j]=arrayOfArrays[j][Math.floor(remainder/placeValues[j])];
remainder=remainder%placeValues[j];
}
permutations.push(permutation.reduce((prev,curr)=>prev+curr,"")); }
return permutations;
}
First express arrays as array of arrays:
arrayOfArrays=[["A","B","C"],["a","b","c","d"],["1","2"]];
Next work out the number of permuations in the solution by multiplying the number of elements in each array by each other:
//["A","B","C"].length*["a","b","c","d"].length*["1","2"].length //24 permuations
Then give each array a place value, starting with the last:
//["1","2"] place value 1
//["a","b","c","d"] place value 2 (each one of these letters has 2 possibilities to the right i.e. 1 and 2)
//["A","B","C"] place value 8 (each one of these letters has 8 possibilities to the right i.e. a1,a2,b1,b2,c1,c2,d1,d2
placeValues=[8,2,1]
This allows each element to be represented by a single digit:
arrayOfArrays[0][2]+arrayOfArrays[1][3]+arrayOfArrays[2][0] //"Cc1"
...would be:
2*placeValues[2]+3*placesValues[1]+0*placeValues[2] //2*8+3*2+0*1=22
We actually need to do the reverse of this so convert numbers 0 to the number of permutations to an index of each array using quotients and remainders of the permutation number.
Like so:
//0 = [0,0,0], 1 = [0,0,1], 2 = [0,1,0], 3 = [0,1,1]
for(let i=0;i<permutationCount;i++)
{
remainder=i;
permutation=[];
for(let j=0;j<arrayOfArrays.length;j++)
{
permutation[j]=arrayOfArrays[j][Math.floor(remainder/placeValues[j])];
remainder=remainder%placeValues[j];
}
permutations.push(permutation.join(""));
}
The last bit turns the permutation into a string, as requested.
Make a loop like this
->
let numbers = [1,2,3,4,5];
let letters = ["A","B","C","D","E"];
let combos = [];
for(let i = 0; i < numbers.length; i++) {
combos.push(letters[i] + numbers[i]);
};
But you should make the array of “numbers” and “letters” at the same length thats it!
I would like to merge 2 arrays with a different length:
let array1 = ["a", "b", "c", "d"];
let array2 = [1, 2];
The outcome I would expect is ["a", 1 ,"b", 2, "c", "d"]
What's the best way to do that?
You could iterate the min length of both array and build alternate elements and at the end push the rest.
var array1 = ["a", "b", "c", "d"],
array2 = [1, 2],
result = [],
i, l = Math.min(array1.length, array2.length);
for (i = 0; i < l; i++) {
result.push(array1[i], array2[i]);
}
result.push(...array1.slice(l), ...array2.slice(l));
console.log(result);
Solution for an arbitrary count of arrays with a transposing algorithm and later flattening.
var array1 = ["a", "b", "c", "d"],
array2 = [1, 2],
result = [array1, array2]
.reduce((r, a) => (a.forEach((a, i) => (r[i] = r[i] || []).push(a)), r), [])
.reduce((a, b) => a.concat(b));
console.log(result);
Here's another way you can do it using destructuring assignment -
const interleave = ([ x, ...xs ], ys = []) =>
x === undefined
? ys // base: no x
: [ x, ...interleave (ys, xs) ] // inductive: some x
console.log (interleave ([0, 2, 4, 6], [1, 3, 5])) // [ 0 1 2 3 4 5 6 ]
console.log (interleave ([0, 2, 4], [1, 3, 5, 7])) // [ 0 1 2 3 4 5 7 ]
console.log (interleave ([0, 2, 4], [])) // [ 0 2 4 ]
console.log (interleave ([], [1, 3, 5, 7])) // [ 1 3 5 7 ]
console.log (interleave ([], [])) // [ ]
And another variation that supports any number of input arrays -
const interleave = ([ x, ...xs ], ...rest) =>
x === undefined
? rest.length === 0
? [] // base: no x, no rest
: interleave (...rest) // inductive: no x, some rest
: [ x, ...interleave(...rest, xs) ] // inductive: some x, some rest
console.log (interleave ([0, 2, 4, 6], [1, 3, 5])) // [ 0 1 2 3 4 5 6 ]
console.log (interleave ([0, 2, 4], [1, 3, 5, 7])) // [ 0 1 2 3 4 5 7 ]
console.log (interleave ([0, 2, 4], [])) // [ 0 2 4 ]
console.log (interleave ([], [1, 3, 5, 7])) // [ 1 3 5 7 ]
console.log (interleave ([], [])) // [ ]
Create an array of tuples. Each tuple contains 1 element from each array, flatten by spreading the array of tuples, and adding the leftover items from the arrays:
const a1 = ["a", "b", "c", "d"];
const a2 = [1,2];
const l = Math.min(a1.length, a2.length);
const merged = [].concat(...Array.from({ length: l }, (_, i) => [a1[i], a2[i]]), a1.slice(l), a2.slice(l));
console.log(merged);
Here's a modern solution that takes any number of arrays:
const braidArrays = (...arrays) => {
const braided = [];
for (let i = 0; i < Math.max(...arrays.map(a => a.length)); i++) {
arrays.forEach((array) => {
if (array[i] !== undefined) braided.push(array[i]);
});
}
return braided;
};
Note that you could change Math.max to Math.min to only include up to the shortest array.
Here's a sample I/O:
braidArrays(['a','b','c','d'], [1,2,3], [99,98,97,96,95]);
// ['a', 1, 99, 'b', 2, 98, 'c', 3, 97, 'd', 96, 95]
Another ONELINER:
const merge = (arr1, arr2) => ((arr1.length > arr2.length) ? arr1 : arr2).map((_,i)=>[arr1[i],arr2[i]]).flat().filter(Boolean);
explanation:
Take the longest array with the ternary conditional operator
Use map to create for each index a pair of elements from each array
Flatten the result
Remove the undefined
In case someone is looking for a performance comparison i have done a file which compares some of the above functions.
The test was to merge two arrays with lengths 200 and 500. For each method the test was run 1000 times.
Here are the results ordered by the fastest (time):
6.7ms
9.8ms
16.7ms
23.3ms
24.2ms
151.7ms
297.8ms
1.15s
Link to the file
ONELINER: I assume that x=array1, y=array2, x and y can be arbitrary arr
[...x,...y].reduce((l,c,i)=>(i<x.length&&l.push(x[i]),i<y.length&&l.push(y[i]),l),[])
working example (for 3 cases)
You can do:
const array1 = ["a", "b", "c", "d"];
const array2 = [1, 2];
const mergeArrays = (a, b) => (a.length > b.length ? a : b)
.reduce((acc, cur, i) => a[i] && b[i] ? [...acc, a[i], b[i]] : [...acc, cur], []);
console.log(mergeArrays(array1, array2)); // ["a",1 ,"b", 2, "c", "d"]
This can be done rather simply using a splicing function within reduce:
function splicer(array, element, index) {
array.splice(index * 2, 0, element);
return array;
}
function weave(array1, array2) {
return array1.reduce(splicer, array2.slice());
}
let array1 = ["a", "b", "c", "d"];
let array2 = [1, 2];
let outcome = weave(array1, array2);
console.log(outcome);
A bit verbose solution that lets you choose which array goes first
const a = ['a', 'b', 'c'];
const b = [1, 4];
const combineAlternatingArrays = (a, b) => {
let combined = [];
const [shorter, larger] = [a, b].sort((a, b) => a.length -b.length);
shorter.forEach((item, i) => {
combined.push(larger[i], item);
})
combined.push(...larger.slice(shorter.length));
return combined;
}
console.log(combineAlternatingArrays(a, b));
It is also possible to use a reduce, but the syntax is less clear in my opinnion.
const a = ['a', 'b', 'c'];
const b = [1, 4];
const combineAlternatingArrays = (a, b) => {
const [shorter, larger] = [a, b].sort((a, b) => a.length -b.length);
return shorter.reduce(
(combined, next, i, shorter) => {
return (i === (shorter.length -1)) ? [...combined, larger[i], next, ...larger.slice(shorter.length)] : [...combined, larger[i], next];
},
[]
);
}
console.log(combineAlternatingArrays(a, b));
I generally use nullish coalescing operator (??) for such a scenario:
var mergeAlternately = (a, b) => {
const maxLength = Math.max(a.length, b.length);
let result = [];
for (let i = 0; i < maxLength; i++) {
result.push( (a[i] ?? '') , (b[i] ?? ''));
}
// Remove empty array values
return result.filter(item => item);
};
let array1 = ["a", "b", "c", "d"];
let array2 = [1, 2];
console.log(mergeAlternately(array1, array2))
More modern, efficient and shorter way:
const arr1 = ["a", "b", "c", "d"]
const arr2 = [1, 2]
const res = (arr1.length > arr2.length ? arr1 : arr2) // you can replace it with just arr1, if you know its always longer
.flatMap((e, idx) => arr2[idx] ? [e, arr2[idx]] : [e])
console.log(res)
using an iterator:
function *gen(arr1, arr2){
for(let i = 0; i < Math.max(arr1.length, arr2.length); i++) {
if (arr1[i]) yield arr1[i];
if (arr2[i]) yield arr2[i];
}
}
const x = gen(['a','b','c','d'], [1,2]);
const result = [...x];
gives
Array(6) [ "a", 1, "b", 2, "c", "d" ]
Using ES6 generator functions this can be implemented generically for any amount of arrays of any lengths. The key is going through all arrays regardless of length in order and then adding each of the values they have into a single merged array.
By using the iterator protocol of arrays we can uniformly proceed through the items in each array.
When producing some sequence of alternating values of other sequences, that is frequently called an interleave. Sometimes also called a Faro shuffle - it's more widely known with playing cards - a perfect Faro shuffle combines two piles of cards in such a way that cards from each pile alternate. However, this is an example of an interleave sequence and mathematicians also use the term to describe the process of interleaving.
//go through all arrays and produce their values
function* parallelWalkAllArrays(...arrays) {
//get iterator for each array
const iterators = arrays.map(arr => arr[Symbol.iterator]());
let values;
//loop until complete
while (true) {
values = iterators
.map(it => it.next()) //advance iterators
.filter(({done}) => !done) //keep anything that is not finished
.map(({value}) => value); //get the values
//quit if all are exhausted
if (values.length === 0)
return;
//yield a tuple of all values
yield values;
}
}
function interleaveMergeArrays(...arrays) {
//start a generator function
const sequence = parallelWalkAllArrays(...arrays);
let merged = [];
//flatten each result into a single array
for (const result of sequence) {
merged.push(...result)
}
return merged;
}
const array1 = [1, 2, 3, 4, 5];
const array2 = ['a', 'b', 'c', 'd', 'e'];
console.log(
interleaveMergeArrays(array1, array2)
);
const shortArray = ["apple", "banana"];
console.log(
interleaveMergeArrays(array1, shortArray)
);
console.log(
interleaveMergeArrays(shortArray, array2)
);
console.log(
interleaveMergeArrays(array1, shortArray, array2)
);
Alternatively, you can take a very similar approach but directly produce a flat sequence from the generator. That way you can consume it immediately.
//go through all arrays and produce their values
function* walkAllArrays(...arrays) {
//get iterator for each array
const iterators = arrays.map(arr => arr[Symbol.iterator]());
let values;
//loop until complete
while (true) {
values = iterators
.map(it => it.next()) //advance iterators
.filter(({done}) => !done) //keep anything that is not finished
.map(({value}) => value); //get the values
//quit if all are exhausted
if (values.length === 0)
return;
//yield each value
for (const value of values)
yield value;
}
}
const array1 = [1, 2, 3, 4, 5];
const array2 = ['a', 'b', 'c', 'd', 'e'];
console.log(Array.from(
walkAllArrays(array1, array2)
));
const shortArray = ["apple", "banana"];
console.log(Array.from(
walkAllArrays(array1, shortArray)
));
console.log(Array.from(
walkAllArrays(shortArray, array2)
));
console.log(Array.from(
walkAllArrays(array1, shortArray, array2)
));
I personally find the latter approach less flexible, as it only solves this problem. Doing a parallel sequential walk through all arrays can be re-used for other things such as zipping arrays, so having a helper function consume the output of that seems like it can leave more options open. On the other hand having a single function makes it a bit more straightforward to see how it's implemented.