Join array elements if another array have same elements - javascript

I have two arrays
var arrayA = ["a", "a", "b", "b", "a", "c"];
var arrayB = [10, 20, 3, 2, 20, 5];
As you can see, arrayA[0], arrayA[1], arrayA[4] have an same elements (arrayA[2], arrayA[3] same too).
So based on above example, I want arrayB[0], arrayB[1], arrayB[4] will be summed up, and arrayB[2], arrayB[3] too.
expectation output
arrayA = ["a", "b", "c"];
arrayB = [50, 5, 5];
It's possible to sum the arrayB elements if arrayA have same elements based arrayA index? and is there an Lodash/Underscore function to do this?

You could use an object for the indices and maintain the values.
var arrayA = ["a", "a", "b", "b", "a", "c"],
arrayB = [10, 20, 3, 2, 20, 5],
indices = Object.create(null),
groupedA = [],
groupedB = [];
arrayA.forEach(function (a, i) {
if (!(a in indices)) {
groupedA.push(a);
indices[a] = groupedB.push(0) - 1;
}
groupedB[indices[a]] += arrayB[i];
});
console.log(groupedA);
console.log(groupedB);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Version which mutates the original arrays.
var arrayA = ["a", "a", "b", "b", "a", "c"],
arrayB = [10, 20, 3, 2, 20, 5],
indices = Object.create(null),
i = 0;
while (i < arrayA.length) {
if (!(arrayA[i] in indices)) {
indices[arrayA[i]] = i++;
continue;
}
arrayB[indices[arrayA[i]]] += arrayB.splice(i, 1)[0];
arrayA.splice(i, 1);
}
console.log(arrayA);
console.log(arrayB);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Here's a solution using lodash:
[arrayA, arrayB] = _(arrayA)
.zip(arrayB)
.groupBy(0)
.mapValues( grp => _.sumBy(grp,1))
.thru(obj => [_.keys(obj), _.values(obj)])
.value();
zip will associate each element in arrayA with the corresponding element in arrayB e.g. [ ['a', 10], ['a', 20], ...]
We then groupBy the value in position 0 giving an object something like:
{
a: ['a', 10], ['a', 20], ['a', 20'],
b: ['b', 3] ...,
c: ...
}
The values of each key are then mapped to the sum of the values in position 1 before finally returning the keys and the values in separate arrays.

You can reduce both arrays into an ES6 Map, and then spread the keys for arrayA, and the values for arrayB:
const arrayA = ["a", "a", "b", "b", "a", "c"];
const arrayB = [10, 20, 3, 2, 20, 5];
const map = arrayA.reduce((m, c, i) => m.set(c, (m.get(c) || 0) + arrayB[i]), new Map());
const arrA = [...map.keys()];
const arrB = [...map.values()];
console.log(arrA);
console.log(arrB);

With an intermediary "result" object:
var arrayA = ["a", "a", "b", "b", "a", "c"];
var arrayB = [10, 20, 3, 2, 20, 5];
var result = {};
for (var i = 0, max = arrayA.length; i < max; i++) {
if (!result[arrayA[i]]) {
result[arrayA[i]] = 0;
}
result[arrayA[i]] += arrayB[i];
}
var keys = Object.keys(result);
arrayA = [];
arrayB = [];
for (var i = 0, max = keys.length; i < max; i++) {
arrayA.push(keys[i]);
arrayB.push(result[keys[i]]);
}

You can compute sums of all elements in arrayB that corresponds to each element in arrayA, store these sums in an object and use Object.values to get an array of the sums.
var arrayA = ["a", "a", "b", "b", "a", "c"];
var arrayB = [10, 20, 3, 2, 20, 5];
var sum = {};
arrayA.forEach((l, index) => {
sum[l] = (sum[l] || 0) + arrayB[index];
});
var res = Object.values(sum);
console.log(res);
And it can be done even shorter with array.prototype.reduce:
var arrayA = ["a", "a", "b", "b", "a", "c"];
var arrayB = [10, 20, 3, 2, 20, 5];
var res = Object.values(arrayA.reduce((m, l, index) => {
m[l] = (m[l] || 0) + arrayB[index];
return m;
}, {}));
console.log(res);

let _ = require('underscore');
var arrayA = ["a", "a", "b", "b", "a", "c"];
var arrayB = [10, 20, 3, 2, 20, 5];
let res = {};
_.each(arrayA, (item, key) => {
if (! res[item]) {
res[item] = arrayB[key];
} else {
res[item] = res[item] + arrayB[key];
}
});
arrayA = [];
arrayB = [];
_.each(res,(value,key) => {
arrayA.push(key);
arrayB.push(value);
});
console.log(arrayA);
console.log(arrayB);

First fill the dict and then fill de arrays with the keys and values
let arrayA = ["a", "a", "b", "b", "a", "c"];
let arrayB = [10, 20, 3, 2, 20, 5];
let result = {};
for (let i=0; i < arrayA.length; i++ ){
let valueB = 0;
if (arrayB.length > i) {
valueB = arrayB[i];
}
if (result.hasOwnProperty(arrayA[i])) {
result[arrayA[i]] += valueB;
}
else {
result[arrayA[i]] = valueB;
}
}
let resultA = [];
let resultB = [];
for (let k in result) {
resultA.push(k);
resultB.push(result[k]);
}
console.log(resultA);
console.log(resultB);

Use Array#reduce method.
var arrayA = ["a", "a", "b", "b", "a", "c"];
var arrayB = [10, 20, 3, 2, 20, 5];
// reference to keep the index
var ref = {},
// array for keeping first result
res1 = [];
var res2 = arrayA
// iterate over the first array
.reduce(function(arr, v, i) {
// check index presenet in the refernece object
if (!(v in ref)) {
// if not then define the index and insert 0 in the array(defining the new index)
arr[ref[v] = arr.length] = 0;
// push value into the array( for unique value )
res1.push(v);
}
// update the element at the index
arr[ref[v]] += arrayB[i];
// return the array reference
return arr;
// initialize initial value as an empty array to keep result
}, [])
console.log(res1, res2);

Related

Merge multiple arrays based on their index using javascript

I need to merge two arrays into a single array. I have code but it is not working as expected-- it is merging them one after another, but I need to interlock the values.
<html>
<head>
<title></title>
<script src="https://code.jquery.com/jquery-3.1.1.js"></script>
</head>
<body>
<div id="result"></div>
<script type="text/javascript">
var array1 = [1, 2, 3, 4];
var array2 = ["a", "b", "c", "d"];
var newArray = $.merge(array1, array2);
$("#result").html(newArray);
//the result its 1234absd
//ineed result like 1a,2b,3c,4d
</script>
</body>
</html>
You can use Array#map in plain javascript.
var array1 = [1, 2, 3, 4];
var array2 = ["a", "b", "c", "d"];
var newArray = array1.map((e, i) => e + array2[i]);
console.log(newArray);
If you use map on array1 then first parameter is current value of array1 in loop and the second parameter is index of that element in array. So you can match current element in array1 with elements from other arrays with the same index using index.
var array1 = [1, 2, 3, 4];
var array2 = ["a", "b", "c", "d"];
var array3 = ["f", "d", "s", "a"];
var newArray = array1.map(function(value, index) {
return value + array2[index] + array3[index];
});
console.log(newArray);
You could use Array#reduce and Array#map for an arbitrary count of arrays with same length.
var a1 = [1, 2, 3, 4],
a2 = ["a", "b", "c", "d"],
a3 = [9, 8, 7, 6],
a4 = ["z", "y", "x", "w"],
result = [a1, a2, a3, a4].reduce((a, b) => a.map((v, i) => v + b[i]));
console.log(result);
ES5
var a1 = [1, 2, 3, 4],
a2 = ["a", "b", "c", "d"],
a3 = [9, 8, 7, 6],
a4 = ["z", "y", "x", "w"],
result = [a1, a2, a3, a4].reduce(function (a, b) {
return a.map(function (v, i) {
return v + b[i];
});
});
console.log(result);
Just a little variation note, to merge by index in an array multiple, to create an associative array of values.
const a = [1, 2, 3, 4],
b = [34, 54, 54, 43]
console.log(
a.map((e,i) => [e,b[i]])
)
Then later, to search for the closest match from a given value.
On this example, searching for the best associated value for 3.7:
const a = [1, 2, 3, 4],
b = [34, 54, 54, 43],
c = a.map((e,i) => [e,b[i]]),
search = 3.7,
temp = [];
c.forEach(function(e){
temp.push(e[0])
})
const seek = temp.reduce((m, n) => (Math.abs(n-search) < Math.abs(m-search) ? n : m))
const index = c.findIndex((s) => s.indexOf(seek) !== -1)
console.log(c[index][1])
This is not exactly how behave an object.
For example, identical index entries aren't merged but are holding their associated values in a new entry (duplicate index), we can roll Math from the index, and create transformation matrix. We can use any column as index and search both way.
You can use a combination of reduce and concat (source):
var array1 = [1, 2, 3, 4];
var array2 = ["a", "b", "c", "d"];
var newArray = array1.reduce(function(prev, curr) {
return prev.concat(curr, array2[prev.length / 2]);
}, []);
$("#result").html(newArray);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="result"></div>
Well actually JavaScript lacks a .zip() functor which would be very handy for exactly what you are trying to do. Lets invent it;
Array.prototype.zip = function(a){
return this.map((e,i) => typeof e === "object" || typeof a[i] === "object" ? [e,a[i]] : e+a[i]);
};
var arrays = [[1, 2, 3, 4],
["a", "b", "c", "d"],
[9, 8, 7, 6],
["z", "y", "x", "w"],
[[1,2,3],[4,5,6],[7,8,9],[10,11,12]]
],
result = arrays.reduce((p,c) => p.zip(c));
console.log(result);
In case someone needs to merge any number of arrays (with fixed length) in the way mentioned above in the post, following function will be useful
console.clear();
var array1 = ["a1", "b1", "c1", "d1"];
var array2 = ["a2", "b2", "c2", "d2"];
var array3 = ["a3", "b3", "c3", "d3"];
var array4 = ["a4", "b4", "c4", "d4"];
function mergeElementsAtIndex(one_or_more_arrays){
const total_arr = arguments.length;
newArray = arguments[0].map((v, k) => {
var temp_val = v;
for(var i=1; i < total_arr; i++){
temp_val += arguments[i][k];
}
return temp_val;
});
return newArray;
}
merged_array = mergeElementsAtIndex(array1, array2, array3, array4);
console.log(merged_array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
var array1 = [1, 2, 3, 4];
var array2 = ["a", "b", "c", "d"];
var array3 = ["f", "d", "s", "a"];
var newArray = array1.map(function(value, index) {
return value + array2[index] + array3[index];
});
console.log(newArray);

How to merge two arrays in JavaScript and keep their order

I had an whiteboard task that stumped me in the interview, however I have written a solution and wondered if anyone has improvements on it as I'm iterating which the interviewer said not to. The two arrays must be merged with the order being array1[0], array2[0], array1[1], array2[1]... (see expectedResult) etc
const options = [[1, 12, 5], ["a", "b", "c", "d", "e"]]
const expectedResult = [1, "a", 12, "b", 5, "c", "d", "e"]
function mergeArrays(first, second) {
let returnArray = []
first.forEach((value, key) => {
returnArray.push(value)
if (second[key]) returnArray.push(second[key])
if (!first[key + 1] && second[key + 1]) {
returnArray.push(
...second.slice(key + 1, second.length)
)
}
})
return returnArray
}
const result = mergeArrays(options[0], options[1])
console.log(result.toString() === expectedResult.toString(), result)
With reduce (as an alternative to the classical for/while loop control structures)
const options = [[1, 12, 5], ["a", "b", "c", "d", "e"]];
const expectedResult = [1, "a", 12, "b", 5, "c", "d", "e"]
// a is the accumulator
// cV, cI are resp. current value and current index
result = options[0].reduce(function (a, cV, cI) {
return a.concat([cV,options[1][cI]]);
},[]);
result = result.concat(options[1].splice(options[0].length));
console.log(result.toString() === expectedResult.toString(), result)
At each step two elements are added to the accumulator array a using concat.
I go the classic way, with a while loop, because it minimize the checks inside of the loop and appends without another check just the rest of one of the arrays.
function mergeArrays(first, second) {
var min = Math.min(first.length, second.length),
i = 0,
result = [];
while (i < min) {
result.push(first[i], second[i]);
++i;
}
return result.concat(first.slice(min), second.slice(min));
}
const options = [[1, 12, 5], ["a", "b", "c", "d", "e"]];
console.log(mergeArrays(...options));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Instead of using value in if conditions , check for length of array.
Problems I see in code are at conditions
if (second[key]) returnArray.push(second[key])
// will not run if second[key] is 0,null,undefined.
if (!first[key + 1] && second[key + 1])
// will produce unwanted result if value reference is 0,null,undefined.
so instead, check for length would produce better result
So the condition
if (second[key]) returnArray.push(second[key])
can be changed into
if( second.length > key) returnArray.push(second[key])
You can use a recursive zipping function, using spread to feed an array of two into it as its parameters:
var z = (a, b) => a.length ? [a[0], ...z(b, a.slice(1))] : b;
var options =
[
[1, 12, 5],
["a", "b", "c", "d", "e"]
];
var expectedResult = z(...options);
console.log(JSON.stringify(expectedResult));
or for any number of array inputs:
var z = (a = [], ...b) =>
b.length ? a.length ? [a[0], ...z(...b, a.slice(1))] : z(...b) : a;
var options =
[
[1, 2],
'♦♡♣♤♥♢',
['A', 'B', 'C'],
['😊', '😔', '😠'],
[null, NaN, undefined]
];
var expectedResult = z(...options);
var stringify = (o) => JSON.stringify(o, (k, v) => v === undefined ? '__undefined__' : v !== v ? '__NaN__' : v).replace(/"__undefined__"/g, 'undefined').replace(/"__NaN__"/g, 'NaN');
console.log(stringify(expectedResult));

Sort function doesn't work on object

I'm trying to sort an object in JS, but the sort function doesn't seem to work for my object.
I want to sort my array by frequency, descending.
["B", "A", "C", "C", "A"] will result in [C: 2, A: 2, B: 1]
Here's a fiddle: http://jsfiddle.net/64q43/
var arr = ["B", "A", "C", "C", "A"];
var arrFrequency = [];
arr.forEach(function(value) {
arrFrequency[value] = 0;
});
console.log(arrFrequency);
// [B: 0, A: 0, C: 0]
arr.filter(function(value) {
if (arrFrequency.hasOwnProperty(value)) arrFrequency[value]++;
});
console.log(arrFrequency);
// [B: 1, A: 2, C: 2]
Objects don't have any defined order. However, you can first create a an object like this:
var arr = ["B", "A", "C", "C", "A"];
var freq = {};
arr.forEach(function(value) {
freq[value] = (freq[value] || 0) + 1;
});
console.log(freq); // {B: 1, A: 2, C: 2}
And then map the keys of this object to an array:
var arrFrequency = [];
for(var k in freq) {
arrFrequency.push({ key: k, count: freq[k] });
};
console.log(arrFrequency); // [{"key":"B","count":1},{"key":"A","count":2},{"key":"C","count":2}]
And now sort it like this:
arrFrequency.sort(function(x, y) {
return y.count - x.count;
});
console.log(arrFrequency); // [{"key":"A","count":2},{"key":"C","count":2},{"key":"B","count":1}]
Demonstration
My this form of sorting function helps you:
// sort objects according to their key-names (a,b,c):
function sortObjArray(array){
for(var k=1; k < array.length; k++){
for(var i=k; i > 0; i--){
var formerLetter = Object.keys(array[i-1]);
var currLetter = Object.keys(array[i]);
if( currLetter < formerLetter ){
[x,y] = [array[i],array[i-1]];
[array[i],array[i-1]] = [y,x];
}
}
}
return array;
}
var array = [{ b: 4},{ c: 1},{ a: 3}];
sortObjArray(array);//[Object { a=3}, Object { b=4}, Object { c=1}]
// or in descending order, according to their occurrence
function sortObjArray2(array){
for(var k=1; k < array.length; k++){
for(var i=k; i > 0; i--){
var formerLetter = Object.keys(array[i-1])[0];
var currLetter = Object.keys(array[i])[0];
var formerValue = array[i-1][formerLetter];
var currValue = array[i][currLetter];
if( currValue> formerValue ){
[x,y] = [array[i],array[i-1]];
[array[i],array[i-1]] = [y,x];
}
}
}
return array;
}
var array = [{ b: 4},{ c: 1},{ a: 3}];
sortObjArray2(array);//[Object { b=4}, Object { a=3}, Object { c=1}]

Javascript: Sort multi dimensional Array based on first arrays sort results

I have an array setup similar to this:
var ary1 = new Array("d", "a", "b", "c");
var ary2 = new Array("ee", "rr", "yy", "mm");
var mdAry = new Array(ary1, ary2);
ary1 and ary2 indexes are info related to each other in the grand scheme of things.
d ee
a rr
b yy
c mm
I can sort() ary1 and get:
a
b
c
d
but if I sorted ary2 independently I would get:
ee
mm
rr
yy
which visually breaks ary1 and ary2 connections when listed out. Can I retrieve ary1's sorted solution and apply that to ary2? I want to get this:
a rr
b yy
c mm
d ee
If not, could mdAry be sorted so that it applies mdAry[0] sorted solution to the remaining indicies?
If your array items are related, then store them together:
var arr = [
{x: 'd', y: 'ee'},
{x: 'a', y: 'rr'},
{x: 'b', y: 'yy'},
{x: 'c', y: 'mm'}
];
arr.sort(function(a, b) {
if (a.x != b.x) {
return a.x < b.x ? -1 : 1;
}
return 0;
});
One way to do this is to transform the data structure to something that can be sorted more easily, and then transform it back after
var ary1 = ["d", "a", "b", "c"],
ary2 = ["ee", "rr", "mm", "yy"]
mdAry = [ary1, ary2];
// convert to form [[d, ee], [a, rr], ..]
var tmp = mdAry[0].map(function (e, i) {
return [e, mdAry[1][i]];
});
// sort this
tmp.sort(function (a, b) {return a[0] > b[0];});
// revert to [[a, b, ..], [rr, mm, ..]]
tmp.forEach(function (e, i) {
mdAry[0][i] = e[0];
mdAry[1][i] = e[1];
});
// output
mdAry;
// [["a", "b", "c", "d"], ["rr", "mm", "yy", "ee"]]
Just to add yet another method in there, you could get a sort "result" from the first array and apply that to any other related list:
function getSorter(model) {
var clone = model.slice(0).sort();
var sortResult = model.map(function(item) { return clone.indexOf(item); });
return function(anyOtherArray) {
result = [];
sortResult.forEach(function(idx, i) {
result[idx] = anyOtherArray[i];
});
return result;
}
}
Then,
var arr = ["d", "a", "b", "c"];
var arr2 = ["ee", "rr", "yy", "mm"];
var preparedSorter = getSorter(arr);
preparedSorter(arr2);
//=> ["rr", "yy", "mm", "ee"];
Or,
multidimensional = [arr, arr2];
multidimensional.map(getSorter(arr));
// => [["a", "b", "c", "d"], ["rr", "yy", "mm", "ee"]]
You could "merge" them into a single object with two properties, sort by the first one, and then separate back in the end (see demo here):
function sortBoth(ary1, ary2) {
var merged = [];
for (var i=0; i < ary1.length; i++) merged.push({'ary1': ary1[i], 'ary2': ary2[i]});
merged.sort(function(o1, o2) { return ((o1.ary1 < o2.ary1) ? -1 : ((o1.ary1 == o2.ary1) ? 0 : 1)); });
for (var i=0; i < merged.length; i++) { ary1[i] = merged[i].ary1; ary2[i] = merged[i].ary2; }
}
var ary1 = new Array("d", "a", "b", "c");
var ary2 = new Array("ee", "rr", "mm", "yy");
console.log(ary1);
console.log(ary2);
sortBoth(ary1, ary2);
console.log(ary1);
console.log(ary2);
Output:
[ "d", "a", "b", "c"]
["ee", "rr", "mm", "yy"]
[ "a", "b", "c", "d"]
["rr", "mm", "yy", "ee"]
In your example the result should be (if I understand correctly)
a rr
b mm
c yy
d ee
So this one should to the job:
Array.prototype.sortRelated = function(related) {
var clone = this.slice(0),
sortedRelated = [];
clone.sort();
for(var i = 0; i < this.length; i ++) {
sortedRelated[clone.indexOf(this[i])] = related[i];
}
return sortedRelated;
}
var ary1 = new Array("d", "a", "b", "c");
var ary2 = new Array("ee", "rr", "mm", "yy");
var sorted = ary1.sortRelated(ary2);
Working demo here: http://jsfiddle.net/cwgN8/

How can I create every combination possible for the contents of two arrays?

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!

Categories

Resources