How to slice array values by two - javascript

I have a javascript array of values [A,B,C,D,E.....] that i need to split with this syntax:
[ [A, B], [C, D], [E, F] ]
and so on (it will be always a pair).
So it should be a loop that returns a string.
I've tried to do like so, and it's almost what i wanted:
text = '['+array[0]+','+array[1]+']';
for (index = 2; index < array.length; index++) {
text += '['+array[index]+','+array[index+1]+']';
console.log(text);
}
I get
[10:00,15:45][18:30,20:00]
[10:00,15:45][18:30,20:00][20:00,undefined]
instead i need to get only this:
[10:00,15:45][18:30,20:00]

Could try making text an actual array and pushing pairs in, like so
var orig=['A','B','C','D','E','F'];
var text=[];
for (index = 0; index < orig.length-1; index+=2) {
text.push([orig[index],orig[index+1]]);
console.log(JSON.stringify(text));
}

Here's a small method for grouping array elements. You can just check that your inner arrays have a length of 2 afterwards, if you only care about pairs.
if (!Array.prototype.group) {
Array.prototype.group = function (length) {
var a = this.slice(), r = [];
length = length || 1;
while (a.length > 0) {
r.push(a.splice(0, length));
}
return r;
};
}
var arr = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'].group(2);
console.log(arr);
A version for if you want to discard a final group that doesn't match your expected length.
if (!Array.prototype.group) {
Array.prototype.group = function (length, req) {
var a = this.slice(), r = [], h;
length = length || 1;
while (a.length > 0) {
h = a.splice(0, length);
if (!req || h.length === length) {
r.push(h);
}
}
return r;
};
}
var arr = ['an', 'odd', 'number', 'of', 'elements'].group(2, true);
console.log(arr)

Something in the lines of:
var groupArray = function(arr){
var groupedArray = [];
for(var i = 0, len = arr.length; i < len; i += 2){
groupedArray.push([arr[i], arr[i + 1]]);
}
return groupedArray;
};
This obviously only works for arrays with an even number of elements. If you need the function to work with arrays with an odd number of elements, too, you need to acomodate for that.

Related

Swapping all elements of an array except for first and last

I have an array that looks like this
const x = ['A','B','C','D','E']
I want to have an elegant function that would shuffle the content of the array but keeps the first or the last element fixed. Something like customShuffle(x) which will shuffle the array but ensures that the element "A" will be in the first position and the element "E" will be at the last position. All other elements are shuffled.
If the first and last elements of the array always stay in that same place, you can apply a normal shuffling algorithm, like a modern variation of Fisher and Yates', skipping those positions:
function customShuffle(arr) {
if (arr.length < 3) {
return arr;
}
// Note the -2 (instead of -1) and the i > 1 (instead of i > 0):
for (let i = arr.length - 2; i > 1; --i) {
const j = 1 + Math.floor(Math.random() * i);
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
console.log(customShuffle([1, 2, 3, 4, 5]).join(', '));
console.log(customShuffle(['A', 'B', 'C', 'D', 'E']).join(', '));
.as-console-wrapper {
max-height: 100vh;
}
Otherwise, if you want to choose the first and last elements, as you pointed out in your original question, you can do something like this:
Find the index of the elements you want to have in the first and last positions first: firstIndex and lastIndex.
If those elements exist (they might not be present), remove them from the array.
Apply a shuffling algorithm to the remaining elements (there's no need to also shuffle first and last).
Add the first and last elements back into their place, if you need to.
function customShuffle(arr, first, last) {
// Find and remove first and last:
const firstIndex = arr.indexOf(first);
if (firstIndex !== -1) arr.splice(firstIndex, 1);
const lastIndex = arr.indexOf(last);
if (lastIndex !== -1) arr.splice(lastIndex, 1);
// Normal shuffle with the remainign elements using ES6:
for (let i = arr.length - 1; i > 0; --i) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
// Add them back in their new position:
if (firstIndex !== -1) arr.unshift(first);
if (lastIndex !== -1) arr.push(last);
return arr;
}
console.log(customShuffle([1, 2, 3, 4, 5], 5, 1).join(', '));
console.log(customShuffle(['A', 'B', 'C', 'D', 'E'], 'E', 'C').join(', '));
console.log(customShuffle([1, 2, 3, 4, 5], 10, 20).join(', '));
.as-console-wrapper {
max-height: 100vh;
}
Using the shuffle algorithm from How to randomize (shuffle) a JavaScript array?
You can extend it like this:
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
function customShuffle(array, first, last) {
if (first) {
if (last) {
const updatedArray = shuffle(array).filter(item => item !== first && item !== last);
return [first, ...updatedArray, last];
}
const updatedArray = shuffle(array).filter(item => item !== first);
return [first, ...updatedArray];
}
return shuffle(array);
}
You could first generate new shuffled array and then check if first and last arguments are provided and take those elements and place them on first and last position.
const x = ['A', 'B', 'C', 'D', 'E']
function shuffle(arr, first, last) {
const newArr = arr.reduce((r, e, i) => {
const pos = parseInt(Math.random() * (i + 1))
r.splice(pos, 0, e)
return r;
}, []);
if (first) newArr.unshift(newArr.splice(newArr.indexOf(first), 1)[0]);
if (last) newArr.push(newArr.splice(newArr.indexOf(last), 1)[0])
return newArr
}
console.log(shuffle(x))
console.log(shuffle(x, "A", "E"))
You can do it like this. first and last params are optional.
Check if first is passed and if it is in the array. If so, then remove it from the array. Do the same for the last. Shuffle indices of the remaining array. Recreate new array based on the shuffled indices, as well as first and last arguments.
const shuffle = (arr, first, last) => {
let firstIn = false;
let lastIn = false;
if (first && arr.includes(first)) {
arr.splice(arr.indexOf(first), 1);
firstIn = true;
}
if (last && arr.includes(last)) {
arr.splice(arr.indexOf(last), 1);
lastIn = true;
}
const len = arr.length;
const used = [];
while (used.length !== len) {
let r = Math.floor(Math.random() * len);
if (!used.includes(r)) { used.push(r); }
}
const newArr = [];
if (first && firstIn) { newArr.push(first); }
for (let i = 0; i < len; i++) {
newArr.push(arr[used[i]]);
}
if (last && lastIn) { newArr.push(last); }
return newArr;
}
let arr = ['A', 'B', 'C', 'D', 'F'];
arr = shuffle(arr);
console.log(arr);
arr = shuffle(arr, 'A');
console.log(arr);
arr = shuffle(arr, 'A', 'B');
console.log(arr);
shuffle(arr); will shuffle the whole array.
arr = shuffle(arr, 'A'); will move A to the front and shuffle the rest.
arr = shuffle(arr, 'A', 'B'); will move A to the front, B to the end, and shuffle the rest.
Word of caution: while this approach is not in-place, it will still mutate the original array, because of the splice method.
Try something like this. It keeps the first and last elements in place without explicitly defining their values, and builds a new array with the other elements shuffled randomly.
const x = ['A','B','C','D','E'];
const shuffledArray = customShuffle(x);
console.log(shuffledArray);
function customShuffle(arr) {
let newArray = [];
const first = arr[0];
const last = arr[arr.length-1];
//First, remove the 'first' and 'last' values from array:
for(let i=0; i<arr.length; i++){
if(arr[i] == first || arr[i] == last){
arr.splice(i, 1);
}
}
//Next, add values to the new array at random:
for(let i=0; i<arr.length; i++){
const indexToRemove = Math.floor( Math.random() * arr.length );
const value = arr[indexToRemove];
arr.splice(indexToRemove, 1);
newArray.push(value);
}
//Last, add in the 'first' and 'last' values:
newArray.unshift(first);
newArray.push(last);
return newArray;
}
Please try the following simple solution.This will shuffle all the elements other than the first and the last element of the array (jsfiddle):
const x = ['A', 'B', 'C', 'D', 'E'];
CustomShuffle(x);
function CustomShuffle(x) {
//shuffle the elements in between first and the last
var max = x.length - 2;
var min = 1;
for (var i = max; i >= min; i--) {
var randomIndex = Math.floor(Math.random() * (max - min + 1)) + min;
var itemAtIndex = x[randomIndex];
x[randomIndex] = x[i];
x[i] = itemAtIndex;
}
alert(x);
}
In case first and last elements are not in place beforehand, you may try the following (jsfiddle):
const x = ['A', 'B', 'C', 'D', 'E'];
CustomShuffle(x, first = "B", last = "A");
function CustomShuffle(x, first, last) {
//position first element correctly
var indexToSwap = x.indexOf(first);
if (indexToSwap != 0) {
x = SwapValuesAtIndices(x, indexToSwap, 0);
}
//position last element correctly
indexToSwap = x.indexOf(last);
if (indexToSwap != x.length - 1) {
x = SwapValuesAtIndices(x, indexToSwap, x.length - 1);
}
//randomly shuffle the remaining elements in between
var max = x.length - 2;
var min = 1;
for (var i = max; i >= min; i--) {
var randomIndex = Math.floor(Math.random() * (max - min + 1)) + min;
var itemAtIndex = x[randomIndex];
x[randomIndex] = x[i];
x[i] = itemAtIndex;
}
alert(x);
}
function SwapValuesAtIndices(array, firstIndex, secondIndex) {
var temp = array[firstIndex];
array[firstIndex] = array[secondIndex];
array[secondIndex] = temp;
return array;
}
Further reading:
Shuffling an array
Generating a random number in a given
range
Swapping elements
You can use this function which uses the modern version of the Fisher–Yates shuffle algorithm to shuffle the sub-array x.slice(1, x.length - 1), which is x with the exclusion of the first and last elements, then adds them back to the shuffled sub-array:
const x = ['A','B','C','D','E'];
function customShuffle(x) {
var y = x.slice(1, x.length - 1);
var j, t, i;
for (i = y.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
t = y[i];
y[i] = y[j];
y[j] = t;
}
return [x[0]].concat(y).concat(x[x.length-1]);
}
console.log(customShuffle(x));
console.log(customShuffle(x));
console.log(customShuffle(x));
console.log(customShuffle(x));
Because you asked for elegant, I like to implement a more functional style of programming here. The code below does what you want. You supple the shuffle function with your array, the max number of times you want it shuffled (the higher the number, the better the shuffle is), and true to keep the first element in place, false to keep the last.
function shuffle(array, maxTimes, first) {
var temp = (first) ? array.reverse().pop() : array.pop();
Array.from(
Array(Math.round(Math.random()*maxTimes))
.keys()).forEach(val => array = array.reduce((acc,val) =>
(Math.random() > 0.5) ? acc.concat([val]) : [val].concat(acc),[]));
return (first) ? [temp].concat(array.reverse()) : array.concat([temp]);
}
Example usage:
shuffle(['A','B','C','D','E'], 10, true);
Output: ["A", "C", "D", "B", "E"]
I hope this is what you're looking for and answers your question.
Edit
Turns out you can get the shuffle logic all in one line (when removing the unnecessary newlines). When you add the two lines to retain the first or last character, you can essentially create this function with three lines of code.
function SpecialShuffle(MyArray)
{
var newArray = [];
var counter= 1;
for(var i = MyArray.length-1 ; i>-1 ; i--)
{
if(i == MyArray.length)
{
newArray[i] = MyArray[i]
}
else if(i == 0 )
{
newArray[i]=MyArray[i];
}
else
{
newArray[counter] = MyArray[i];
counter++;
}
}
return newArray;
}

EloquentJavascript: reverseArrayInPlace function. Is this an acceptable code or would this be considered poor logic? [duplicate]

I'm trying to solve the following exercise:
Reverse an array without using the reverse method, without using a
second array, and without duplicating any of the values.
I've thought about making the array an object and then updating the array from the end to the beginning but I figured you can just update it as well.
Tried something simple like:
function reverseArray(array) {
for (var i = 0; i < array.length; i++) {
// var elem = array.shift();
var elem = array.shift()
array.push(elem)
}
return array
}
array = ['a', 'b','c','d','e'];
reverseArray(array);
But that doesn't really change it. Any advice or explanation on how to do this?
With ES6 syntax you don't need to copy a value into a temporary variable (is that what the last requirement is about?).
function reverse(arr) {
for(let i = 0, j = arr.length-1; i < j; i++, j--)
[arr[i], arr[j]] = [arr[j], arr[i]];
}
const arr = ['a','b','c','d','e'];
reverse(arr);
console.log(arr);
One may argue that arrays are created here (if engines don't optimise this away), just like splice also creates an array (as its return value).
array = ['a', 'b', 'c', 'd', 'e'];
console.log(array);
for (var i = 0; i < Math.floor(array.length / 2); i++) {
var item = array[i];
array[i] = array[array.length - i - 1];
array[array.length - i - 1] = item;
}
console.log(array);
Here is a minimal approach. Given var arr = [1,2,3,4], this loop will mutate arr to [4,3,2,1]:
for (var i = 0; i < arr.length - 1; i++) {
arr.splice(i, 0, arr.pop());
}
The following will work reverse an array without using the reverse method. It works by swapping the first and last elements, then the second and second-to-last elements, then the third and third-to-last elements, etc until the i is no longer less than (<) than j.
function reverse(arr) {
for(var i = 0, j = arr.length-1; i < j; i++, j--) {
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
return arr;
};
var reversed = reverse(['a','b','c','d','e']);
console.log(reversed);
https://jsfiddle.net/pa2fqa8n/1/
a = ['a', 'b', 'c', 'd', 'e'];
for(var i = 0; i < a.length-1; i++){
for(var j = 0; j < a.length-i-1; j++){
var k = a[j];
a[j] = a[j+1];
a[j+1] = k;
}
}
First iteration of inner loop moves the first element to the end, and the rest of the elements forward once. Each following iteration does the same thing, but 1 less than the previous iteration.
I had to use a swap variable, does that violate "without duplicating any of the values"?
var test1 = [2, '5', 6, 'a', 'Z'];
var test2 = [2, '5', false, 'a', 'Z', {a: 'whatevs'}];
console.log('test1 before', JSON.stringify(test1));
console.log('test2 before', JSON.stringify(test2));
reversarooni(test1);
reversarooni(test2);
console.log('test1 after', JSON.stringify(test1));
console.log('test2 after', JSON.stringify(test2));
function reversarooni(inputArray) {
var index = 0;
var len = inputArray.length;
for(; index < len / 2; index++) {
var swap = inputArray[index];
inputArray[index] = inputArray[(len - 1) - index];
inputArray[(len - 1) - index] = swap;
}
}
Here's how, without copies, temporary arrays or variables to hold values, or using Array.reverse().Modifying the array in place
function reverseArray(array) {
var len = array.length;
for (var i=len,j=-1; j++,i--;) array.unshift( array[len-1-i+(j)] );
array.length = len;
}
var array = ['a', 'b','c','d','e'];
reverseArray(array);
console.log(array);
It inserts the values backwards into the beginning of the array, pushing the old values to the end, and then slicing them of by resetting the arrays length after the iteration has completed.
You could use a spread syntax ..., rest parameters ... and return the swapped items with a recursive and functional approach.
const
_ = (...a) => a,
rev = (a, ...rest) => rest.length ? _(...rev(...rest), a) : _(a),
reverseArray = array => rev(...array);
console.log(reverseArray(['a', 'b', 'c', 'd', 'e']));
console.log(reverseArray(['a']));
console.log(reverseArray(['a', 'b']));
.as-console-wrapper { max-height: 100% !important; top: 0; }
function reverseArray(a) {
const halfLength = a.length / 2;
for (let i = 0; i< halfLength; i++){
const start = a[i]
a[i] = a[a.length-i-1]
a[a.length-i-1] = start
}
return a;
}
function printReverse(array) {
for (i = array.length-1; i > -1; i--) {
console.log(array[i]); //4,3,2,1
}
}
printReverse([1, 2, 3, 4]);
This worked for me.

Reversing an array without 'reverse' or duplicating an array

I'm trying to solve the following exercise:
Reverse an array without using the reverse method, without using a
second array, and without duplicating any of the values.
I've thought about making the array an object and then updating the array from the end to the beginning but I figured you can just update it as well.
Tried something simple like:
function reverseArray(array) {
for (var i = 0; i < array.length; i++) {
// var elem = array.shift();
var elem = array.shift()
array.push(elem)
}
return array
}
array = ['a', 'b','c','d','e'];
reverseArray(array);
But that doesn't really change it. Any advice or explanation on how to do this?
With ES6 syntax you don't need to copy a value into a temporary variable (is that what the last requirement is about?).
function reverse(arr) {
for(let i = 0, j = arr.length-1; i < j; i++, j--)
[arr[i], arr[j]] = [arr[j], arr[i]];
}
const arr = ['a','b','c','d','e'];
reverse(arr);
console.log(arr);
One may argue that arrays are created here (if engines don't optimise this away), just like splice also creates an array (as its return value).
array = ['a', 'b', 'c', 'd', 'e'];
console.log(array);
for (var i = 0; i < Math.floor(array.length / 2); i++) {
var item = array[i];
array[i] = array[array.length - i - 1];
array[array.length - i - 1] = item;
}
console.log(array);
Here is a minimal approach. Given var arr = [1,2,3,4], this loop will mutate arr to [4,3,2,1]:
for (var i = 0; i < arr.length - 1; i++) {
arr.splice(i, 0, arr.pop());
}
The following will work reverse an array without using the reverse method. It works by swapping the first and last elements, then the second and second-to-last elements, then the third and third-to-last elements, etc until the i is no longer less than (<) than j.
function reverse(arr) {
for(var i = 0, j = arr.length-1; i < j; i++, j--) {
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
return arr;
};
var reversed = reverse(['a','b','c','d','e']);
console.log(reversed);
https://jsfiddle.net/pa2fqa8n/1/
a = ['a', 'b', 'c', 'd', 'e'];
for(var i = 0; i < a.length-1; i++){
for(var j = 0; j < a.length-i-1; j++){
var k = a[j];
a[j] = a[j+1];
a[j+1] = k;
}
}
First iteration of inner loop moves the first element to the end, and the rest of the elements forward once. Each following iteration does the same thing, but 1 less than the previous iteration.
I had to use a swap variable, does that violate "without duplicating any of the values"?
var test1 = [2, '5', 6, 'a', 'Z'];
var test2 = [2, '5', false, 'a', 'Z', {a: 'whatevs'}];
console.log('test1 before', JSON.stringify(test1));
console.log('test2 before', JSON.stringify(test2));
reversarooni(test1);
reversarooni(test2);
console.log('test1 after', JSON.stringify(test1));
console.log('test2 after', JSON.stringify(test2));
function reversarooni(inputArray) {
var index = 0;
var len = inputArray.length;
for(; index < len / 2; index++) {
var swap = inputArray[index];
inputArray[index] = inputArray[(len - 1) - index];
inputArray[(len - 1) - index] = swap;
}
}
Here's how, without copies, temporary arrays or variables to hold values, or using Array.reverse().Modifying the array in place
function reverseArray(array) {
var len = array.length;
for (var i=len,j=-1; j++,i--;) array.unshift( array[len-1-i+(j)] );
array.length = len;
}
var array = ['a', 'b','c','d','e'];
reverseArray(array);
console.log(array);
It inserts the values backwards into the beginning of the array, pushing the old values to the end, and then slicing them of by resetting the arrays length after the iteration has completed.
You could use a spread syntax ..., rest parameters ... and return the swapped items with a recursive and functional approach.
const
_ = (...a) => a,
rev = (a, ...rest) => rest.length ? _(...rev(...rest), a) : _(a),
reverseArray = array => rev(...array);
console.log(reverseArray(['a', 'b', 'c', 'd', 'e']));
console.log(reverseArray(['a']));
console.log(reverseArray(['a', 'b']));
.as-console-wrapper { max-height: 100% !important; top: 0; }
function reverseArray(a) {
const halfLength = a.length / 2;
for (let i = 0; i< halfLength; i++){
const start = a[i]
a[i] = a[a.length-i-1]
a[a.length-i-1] = start
}
return a;
}
function printReverse(array) {
for (i = array.length-1; i > -1; i--) {
console.log(array[i]); //4,3,2,1
}
}
printReverse([1, 2, 3, 4]);
This worked for me.

Replace an element in an array

Need to replace a value "y" with "#" in an array and also need to count the "y" value in an array.
x = ['a','b','c'];
z = ['z','y','y'];
var a = x.concat(z);
var b = a.sort();
var count = 0;
for(var i = 0; i < b.length; i++){
if(b[i] == "y") {
b['y'] == "#";
var c = count++;
}
}
console.log(b);
console.log(c+1);
Fiddle
declare all variables
assign the right value, do not make a comparison b[i] = "#";,
use count variable, and not c
no need to use b, because sort sorts the given array.
var x = ['a', 'b', 'c'];
var z = ['z', 'y', 'y'];
var a = x.concat(z);
var count = 0;
a.sort();
for (var i = 0; i < a.length; i++){
if (a[i] == "y") {
a[i] = "#";
count++;
}
}
console.log(a);
console.log(count);
You can use reduce with destructuring assignment to effectively return 2 values
var x = ['a','b','c']
var z = ['z','y','y']
var a = x.concat(z)
var [count, b] = a.reduce(([count,b],x)=> {
if (x === 'y')
return [count + 1, [...b, '#']]
else
return [count, [...b, x]]
}, [0, []])
console.log(count) // 2
console.log(b) // ['a', 'b', 'c', 'z', '#', '#']
If you really want the array sorted before replacing 'y' and getting the count, you should know that Array.prototype.sort will mutate the original array. Also, assignment of an array to a variable is by reference.
So when you write this...
var b = a.sort();
You should know that a will be sorted, and b is just a second reference to the exact same array. b is not a separate copy.
If you'd like to make a separate copy, you can do this
// make B a sorted copy of A. do not modify A.
var b = a.slice(0).sort();
== is comparison operator
= is assignment operator
Change to this :
for(var i = 0; i < b.length; i++){
if(b[i] == "y") {
b[i] = "#";
var c = count++;
}
}
from
for(var i = 0; i < b.length; i++){
if(b[i] == "y") {
b['y'] == "#";
count++;
}
}
x = ['a', 'b', 'c'];
z = ['z', 'y', 'y'];
var a = x.concat(z);
var b = a.sort();
var count = 0;
for (var i = 0; i < b.length; i++) {
if (b[i] == "y") {
b[i] = "#";
count++;
}
}
console.log(b);
console.log(count);
You can also use Array#map.
var x = ['a','b','c'];
var z = ['z','y','y'];
var a = x.concat(z);
var b = a.sort();
var count = 0;
b = b.map(function(v) {
if(v == 'y') {
count++;
return '#';
} else {
return v;
}
});
console.log(b);
console.log(count);
I'm not sure, but is this what you want?
x = ['a','b','c'];
z = ['z','y','y'];
var a = x.concat(z);
var b = a.sort();
var count = 0;
for(var i = 0; i < b.length; i++){
if(b[i] == "y") {
b[i] = "#";
var c = count++;
}
}
console.log(b);
console.log(c+1);
jsfiddle
If you are the fan of less line of code, then this one line (or maybe two line) implementation would be noticeable. ;)
BUT the main reason of my answer was about the sort return value.
as mentioned in documentation:
The sort() method sorts the elements of an array in place and returns
the array. The sort is not necessarily stable. The default sort order
is according to string Unicode code points.
So the sort() method do the action in place and returns the result (the array) too.
var x = ['a', 'b', 'c'],
z = ['z', 'y', 'y'],
toReplace = 'y',
replaceWith = '#',
count = 0;
var result = x.concat(z).sort()
.map((v, i, a) => v === toReplace ? ++count && replaceWith : v);
console.log(result, count);

JavaScript - Generating combinations from n arrays with m elements [duplicate]

This question already has answers here:
Cartesian product of multiple arrays in JavaScript
(35 answers)
Closed 1 year ago.
I'm having trouble coming up with code to generate combinations from n number of arrays with m number of elements in them, in JavaScript. I've seen similar questions about this for other languages, but the answers incorporate syntactic or library magic that I'm unsure how to translate.
Consider this data:
[[0,1], [0,1,2,3], [0,1,2]]
3 arrays, with a different number of elements in them. What I want to do is get all combinations by combining an item from each array.
For example:
0,0,0 // item 0 from array 0, item 0 from array 1, item 0 from array 2
0,0,1
0,0,2
0,1,0
0,1,1
0,1,2
0,2,0
0,2,1
0,2,2
And so on.
If the number of arrays were fixed, it would be easy to make a hard coded implementation. But the number of arrays may vary:
[[0,1], [0,1]]
[[0,1,3,4], [0,1], [0], [0,1]]
Any help would be much appreciated.
Here is a quite simple and short one using a recursive helper function:
function cartesian(...args) {
var r = [], max = args.length-1;
function helper(arr, i) {
for (var j=0, l=args[i].length; j<l; j++) {
var a = arr.slice(0); // clone arr
a.push(args[i][j]);
if (i==max)
r.push(a);
else
helper(a, i+1);
}
}
helper([], 0);
return r;
}
Usage:
cartesian([0,1], [0,1,2,3], [0,1,2]);
To make the function take an array of arrays, just change the signature to function cartesian(args) instead of using rest parameter syntax.
I suggest a simple recursive generator function:
// JS
function* cartesianIterator(head, ...tail) {
const remainder = tail.length ? cartesianIterator(...tail) : [[]];
for (let r of remainder) for (let h of head) yield [h, ...r];
}
// get values:
const cartesian = items => [...cartesianIterator(items)];
console.log(cartesian(input));
// TS
function* cartesianIterator<T>(items: T[][]): Generator<T[]> {
const remainder = items.length > 1 ? cartesianIterator(items.slice(1)) : [[]];
for (let r of remainder) for (let h of items.at(0)!) yield [h, ...r];
}
// get values:
const cartesian = <T>(items: T[][]) => [...cartesianIterator(items)];
console.log(cartesian(input));
You could take an iterative approach by building sub arrays.
var parts = [[0, 1], [0, 1, 2, 3], [0, 1, 2]],
result = parts.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []));
console.log(result.map(a => a.join(', ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }
After doing a little research I discovered a previous related question:
Finding All Combinations of JavaScript array values
I've adapted some of the code from there so that it returns an array of arrays containing all of the permutations:
function(arraysToCombine) {
var divisors = [];
for (var i = arraysToCombine.length - 1; i >= 0; i--) {
divisors[i] = divisors[i + 1] ? divisors[i + 1] * arraysToCombine[i + 1].length : 1;
}
function getPermutation(n, arraysToCombine) {
var result = [],
curArray;
for (var i = 0; i < arraysToCombine.length; i++) {
curArray = arraysToCombine[i];
result.push(curArray[Math.floor(n / divisors[i]) % curArray.length]);
}
return result;
}
var numPerms = arraysToCombine[0].length;
for(var i = 1; i < arraysToCombine.length; i++) {
numPerms *= arraysToCombine[i].length;
}
var combinations = [];
for(var i = 0; i < numPerms; i++) {
combinations.push(getPermutation(i, arraysToCombine));
}
return combinations;
}
I've put a working copy at http://jsfiddle.net/7EakX/ that takes the array you gave earlier ([[0,1], [0,1,2,3], [0,1,2]]) and outputs the result to the browser console.
const charSet = [["A", "B"],["C", "D", "E"],["F", "G", "H", "I"]];
console.log(charSet.reduce((a,b)=>a.flatMap(x=>b.map(y=>x+y)),['']))
Just for fun, here's a more functional variant of the solution in my first answer:
function cartesian() {
var r = [], args = Array.from(arguments);
args.reduceRight(function(cont, factor, i) {
return function(arr) {
for (var j=0, l=factor.length; j<l; j++) {
var a = arr.slice(); // clone arr
a[i] = factor[j];
cont(a);
}
};
}, Array.prototype.push.bind(r))(new Array(args.length));
return r;
}
Alternative, for full speed we can dynamically compile our own loops:
function cartesian() {
return (cartesian.cache[arguments.length] || cartesian.compile(arguments.length)).apply(null, arguments);
}
cartesian.cache = [];
cartesian.compile = function compile(n) {
var args = [],
indent = "",
up = "",
down = "";
for (var i=0; i<n; i++) {
var arr = "$"+String.fromCharCode(97+i),
ind = String.fromCharCode(105+i);
args.push(arr);
up += indent+"for (var "+ind+"=0, l"+arr+"="+arr+".length; "+ind+"<l"+arr+"; "+ind+"++) {\n";
down = indent+"}\n"+down;
indent += " ";
up += indent+"arr["+i+"] = "+arr+"["+ind+"];\n";
}
var body = "var res=[],\n arr=[];\n"+up+indent+"res.push(arr.slice());\n"+down+"return res;";
return cartesian.cache[n] = new Function(args, body);
}
var f = function(arr){
if(typeof arr !== 'object'){
return false;
}
arr = arr.filter(function(elem){ return (elem !== null); }); // remove empty elements - make sure length is correct
var len = arr.length;
var nextPerm = function(){ // increase the counter(s)
var i = 0;
while(i < len)
{
arr[i].counter++;
if(arr[i].counter >= arr[i].length){
arr[i].counter = 0;
i++;
}else{
return false;
}
}
return true;
};
var getPerm = function(){ // get the current permutation
var perm_arr = [];
for(var i = 0; i < len; i++)
{
perm_arr.push(arr[i][arr[i].counter]);
}
return perm_arr;
};
var new_arr = [];
for(var i = 0; i < len; i++) // set up a counter property inside the arrays
{
arr[i].counter = 0;
}
while(true)
{
new_arr.push(getPerm()); // add current permutation to the new array
if(nextPerm() === true){ // get next permutation, if returns true, we got them all
break;
}
}
return new_arr;
};
Here's another way of doing it. I treat the indices of all of the arrays like a number whose digits are all different bases (like time and dates), using the length of the array as the radix.
So, using your first set of data, the first digit is base 2, the second is base 4, and the third is base 3. The counter starts 000, then goes 001, 002, then 010. The digits correspond to indices in the arrays, and since order is preserved, this is no problem.
I have a fiddle with it working here: http://jsfiddle.net/Rykus0/DS9Ea/1/
and here is the code:
// Arbitrary base x number class
var BaseX = function(initRadix){
this.radix = initRadix ? initRadix : 1;
this.value = 0;
this.increment = function(){
return( (this.value = (this.value + 1) % this.radix) === 0);
}
}
function combinations(input){
var output = [], // Array containing the resulting combinations
counters = [], // Array of counters corresponding to our input arrays
remainder = false, // Did adding one cause the previous digit to rollover?
temp; // Holds one combination to be pushed into the output array
// Initialize the counters
for( var i = input.length-1; i >= 0; i-- ){
counters.unshift(new BaseX(input[i].length));
}
// Get all possible combinations
// Loop through until the first counter rolls over
while( !remainder ){
temp = []; // Reset the temporary value collection array
remainder = true; // Always increment the last array counter
// Process each of the arrays
for( i = input.length-1; i >= 0; i-- ){
temp.unshift(input[i][counters[i].value]); // Add this array's value to the result
// If the counter to the right rolled over, increment this one.
if( remainder ){
remainder = counters[i].increment();
}
}
output.push(temp); // Collect the results.
}
return output;
}
// Input is an array of arrays
console.log(combinations([[0,1], [0,1,2,3], [0,1,2]]));
You can use a recursive function to get all combinations
const charSet = [["A", "B"],["C", "D", "E"],["F", "G", "H", "I"]];
let loopOver = (arr, str = '', final = []) => {
if (arr.length > 1) {
arr[0].forEach(v => loopOver(arr.slice(1), str + v, final))
} else {
arr[0].forEach(v => final.push(str + v))
}
return final
}
console.log(loopOver(charSet))
This code can still be shorten using ternary but i prefer the first version for readability 😊
const charSet = [["A", "B"],["C", "D", "E"],["F", "G", "H", "I"]];
let loopOver = (arr, str = '') => arr[0].map(v => arr.length > 1 ? loopOver(arr.slice(1), str + v) : str + v).flat()
console.log(loopOver(charSet))
Another implementation with ES6 recursive style
Array.prototype.cartesian = function(a,...as){
return a ? this.reduce((p,c) => (p.push(...a.cartesian(...as).map(e => as.length ? [c,...e] : [c,e])),p),[])
: this;
};
console.log(JSON.stringify([0,1].cartesian([0,1,2,3], [[0],[1],[2]])));

Categories