Find a random combination of arrays with a total length of 10, and splittable into two groups of 5 - javascript

Let's say I have an array with arrays, such as:
const array = [
['a', 'b', 'c'],
['d', 'e'],
['f', 'g', 'h', 'i', 'j'],
['k'],
['l'],
['m'],
['n', 'o', 'p'],
['q', 'r', 's'],
['t', 'u', 'v'],
['x']
];
I want to select any combination at random that respects the following rules:
The total length of all the selected combinations must always be 10. A possible result could be the first 3 items of the array
The selected combinations must be able to be split into two groups of 5. Again, the first 3 items would respect that condition: the length of ['a', 'b, 'c'] + the length of ['d', 'e'] equals 5, and at the same time, the length of ['f', 'g', 'h', 'i', 'j']equals 5. That's two groups of 5. The last 4 elements of the array, on the other hand, wouldn't be able to fulfill this condition, even though they respect the first one (total length = 10).
It might help to know the purpose of this: I have a little multiplayer game. Games need 2 teams of 5 players. And players can enter the game with a friend to play on the same team (or even 5 friends, instantly filling an entire team).
The idea: players would press 'Start'. Then my function would push them into an array like the one above. Each time a push happened, the player/team function (which I'm asking for your help) would run. If a match were found, the game would start.
I have a feeling that this would be best accomplished with some kind of recursive function, but my head is having trouble figuring it out.
After a long couple of hours here's the solution I came up with. Passed all my tests.
//const { shuffle, flatten } = require('lodash');
const pool = [
['a', 'b', 'c'],
['d', 'e'],
['f', 'g', 'h', 'i', 'j'],
['k'],
['l'],
['m'],
['n', 'o', 'p'],
['q', 'r', 's'],
['t', 'u', 'v'],
['x']
];
function getMaxPickSize ( draw ) {
let x = 5;
let y = 5;
draw.forEach( pick => {
if ( x - pick.length >= 0 ) {
x -= pick.length;
} else if ( y - pick.length >= 0 ) {
y -= pick.length;
}
});
return Math.max(x,y);
}
function doDraw( pool ) {
//no need to move further if there arent even 10 players
if ( _.flatten(pool).length < 10 ) {
return false;
}
// keep register of all draws and pools, and items. if we
// figure out an attempt doesnt work, we can go back anytime
// and skip picks that dont work
let prevs = [
// array of objects that will look like this.
// {
// pool: [],
// draw: [],
// skip: []
// }
// ...
];
//let's try. First step, shuffle the pool;
pool = _.shuffle(pool);
function doIt( curr_pool, curr_draw = [], skip_items_w_length ) {
let new_pool = [...curr_pool];
let new_draw = [...curr_draw];
let pick;
if ( skip_items_w_length == undefined ) {
//in first loop it starts here
//if we happen to have luck and fill the draw in
//one go, the else statement below will never execute
pick = new_pool.shift();
} else {
let to_skip = prevs[prevs.length - 1].skip;
to_skip.push(skip_items_w_length);
pick = _.find(new_pool, item => !to_skip.includes(item.length) );
if ( pick ) {
new_pool.splice(new_pool.indexOf(pick), 1);
} else {
if ( !prevs.length ) {
return false;
}
let prev = prevs.pop();
let prev_pool = prev.pool;
let prev_draw = prev.draw;
let last_item_in_prev_draw = prev_draw.pop();
return doIt(prev_pool, prev_draw, last_item_in_prev_draw.length );
}
}
new_draw = [...curr_draw, pick];
//if draw is complete, return it
if ( _.flatten(new_draw).length === 10 ) {
return new_draw;
}
//else draw process continues
//find items in pool that can still fit into draw
const max_pick_size = getMaxPickSize(new_draw);
new_pool = new_pool.filter(item => item.length <= max_pick_size);
//if items dont contain enough players to fill remaining spots,
//repeat this exact step, ignoring items without pick's length
//as none of the remaining picks can follow. if we discover in
// later repeats that no pick allows other picks to follow
// we'll go back 1 step, using previous pool and draw, and
// ignoring all picks with the associated picks length
if ( _.flatten(new_pool).length < 10 - _.flatten(new_draw).length ) {
return doIt(curr_pool, curr_draw, pick.length);
}
prevs.push({
pool: curr_pool,
draw: curr_draw,
skip: []
});
return doIt(new_pool, new_draw);
}
return doIt(pool);
}
const draw = doDraw( pool );
Thank you guys!

Shuffle the array, then take out unique groups till you reach five:
const array = [
['a', 'b', 'c'],
['d', 'e'],
['f', 'g', 'h', 'i', 'j'],
['k'],
['l'],
['m'],
['n', 'o', 'p'],
['q', 'r', 's'],
['t', 'u', 'v'],
['x']
];
function shuffle(arr) { /* Shuffling algorithm here */ }
shuffle(array);
// Extracts arrays with exactly "count" elements, excluding all elements in "exclude" and starting at "start" in the array
// If no combination was found, return undefined
function takeOut(array, count, start = 0, exclude = []) {
// Base case: Count wasn't reached exactly, abort here
if(count < 0) return;
// Base case: Combination was found, go up
if(count === 0) return [];
// Go over the array to find a matching combination
for(let i = start; i < array.length; i++) {
const current = array[i];
// Skip elements that should be excluded
if(exclude.includes(current)) continue;
// Recursive call: Find more elements so that a group of "count" gets reached
const rest = takeOut(array, count - current.length, i + 1, exclude);
if(!rest) continue; // If this element can't be matched up, go on
return [current, ...rest];
}
}
// Our two teams:
const first = takeOut(array, 5);
const second = takeOut(array, 5, 0, first); // all from the first team can't be in the second one
console.log(first, second);
if(first && second)
console.log("The game can start");

I came up with the solution that may not be exactly what you wanted. Anyway I think that it may help you. It finds all possible compositions and if you need only one you can choose it randomly. I also used slightly different data model: object where keys represent team sizes and values are arrays of arrays of teams with according sizes.
const UNITS_NUMBER = 2
// object format: { [number]: how-many-times-this-number-should-be-used }
const COMPOSE_VARIATIONS = [{1: 5}, {1: 3, 2: 1}, {1: 2, 3: 1}, {1: 1, 4: 1}, {1: 1, 2: 2}, {2: 1, 3: 1}, {5: 1}]
const parts = {
1: [['k'], ['l'], ['m'], ['x']],
2: [['d', 'e']],
3: [['a', 'b', 'c'], ['n', 'o', 'p'], ['q', 'r', 's'], ['t', 'u', 'v']],
4: [],
5: [['f', 'g', 'h', 'i', 'j']],
}
function getAllCompositions(allParts, unitsNumber, composeVariations) {
const result = []
const usedPartsStack = []
let units = []
let currentIndex = 0
let unitsComposed = 0
while (currentIndex < composeVariations.length) {
const variation = composeVariations[currentIndex]
if (canCreateUnit(allParts, variation)) {
const unit = getPartsForUnit(allParts, variation)
units.push(flatten(unit))
if (unitsComposed + 1 < unitsNumber) {
usedPartsStack.push({ index: currentIndex, partsUsedForUnit: unit })
allParts = removeUsedParts(allParts, variation)
unitsComposed++
} else {
result.push([...units])
units.pop()
currentIndex++
}
} else {
currentIndex++
}
while (currentIndex === composeVariations.length && usedPartsStack.length) {
const { index, partsUsedForUnit } = usedPartsStack.pop()
currentIndex = index + 1
allParts = restoreUsedParts(allParts, partsUsedForUnit)
unitsComposed--
units.pop()
}
}
return result
}
// checks if passed variation can be used to create unit from parts from allParts object
// unit is a group of parts that forms an array with total length of 5)
function canCreateUnit(allParts, variation) {
return Object.entries(variation).every(([length, count]) => allParts[length].length >= count)
}
// get real parts from allParts object according to variation passed
function getPartsForUnit(allParts, variation) {
const result = []
Object.entries(variation).forEach(([length, count]) => {
result.push(allParts[length].slice(0, count))
})
return result
}
// removes parts that were used for unit creation
function removeUsedParts(allParts, variation) {
const result = { ...allParts }
Object.entries(variation).forEach(([length, count]) => {
result[length] = result[length].slice(count)
})
return result
}
// add parts to allParts object
function restoreUsedParts(allParts, parts) {
const result = { ...allParts }
parts.forEach((item) => {
result[item[0].length] = [...item, ...result[item[0].length]]
})
return result
}
// removes one level of nesting in array
function flatten(partsForUnit) {
const result = []
partsForUnit.forEach(item => {
result.push(...item)
})
return result
}
function print(compositions) {
compositions.forEach(composition => {
composition.forEach(unit => {
console.log(unit)
})
console.log('=======================================')
})
}
print(getAllCompositions(parts, UNITS_NUMBER, COMPOSE_VARIATIONS))

Related

Count occurrence in 2D array and put custom formula result in the end of array

I have a 2D array from a google spreadsheet for example:-
Please Note that the length and width of the 2D array can be of any size. However, the count will always begin with second col1.
var ss = SpreadsheetApp.openById('19xxxxxxxxxxxxxxxxxxxxxxxxUOI');
var sh=ss.getSheetByName("Sheet2");
var lastR = sh.getLastRow()
var data = sh.getRange("A4:QC"+lastR).getValues();
data Array looks like [[alpha,a,a,b,c],[beeta,b,b,a,a],[gama,a,b,c,c],[alpha,d,c,a,a]]
Now I want to count the occurrence of element from each row and push the formula result at the end of every column.
If My formula is (a+c)/(b+d)
From the above example the results are
(2+1)/(1+0) = 3 //occurrence in 1st row a=2,b=1,c=1,d=0
(2+0)/(2+0) = 1 //occurrence in 2nd row a=2,b=2,c=0,d=0
(1+2)/(1+0) = 3 //occurrence in 3rd row a=1,b=1,c=2,d=0
(2+1)/(0+1) = 3 //occurrence in 4th row a=2,b=0,c=1,d=1
Now the result should be at the end of the array
[[alpha,a,a,b,c,3],[beeta,b,b,a,a,1],[gama,a,b,c,c,3],[alpha,d,c,a,a,3]]
First, create a counting function which uses Array.prototype.reduce to count the occurrences of each element in the sub-array. Then, create another function which uses the results of the count to calculate the value according to the formula and then pushes it to the sub-array. Finally, apply this function to each element of your overall array (in this case, it is done in-place but this could also be accomplished by creating a new array and using Array.prototype.map):
const count = (list, els) => {
const acc = {};
els.forEach(el => { acc[el] = 0; });
return list.reduce((acc, curr) => {
acc[curr]++;
return acc;
}, acc);
};
const formula = list => {
const { a, b, c, d } = count(list, ['a', 'b', 'c', 'd']);
const x = (a + c) / (b + d);
list.push(x);
};
const data = [['alpha', 'a', 'a', 'b', 'c'], ['beeta', 'b', 'b', 'a', 'a'], ['gama', 'a', 'b', 'c', 'c'], ['x-ray', 'd', 'c', 'a', 'a']];
data.forEach(formula);
console.log(data);

Shuffle JS array with probability

Say I have an array like so:
const alphabet = ['a', 'b', 'c', 'd'];
This represents 4 political candidates and a rank choice vote, where candidate a is first choice, b is second choice, etc.
I want to shuffle this into a bunch of random orders, but in this case I want a to appear first with probably 60%, b second with probability 20%, and c third with probability 10%, and all the other ordering with probably 10%. Is there some lodash and ramda functionality that can accomplish this or?
This is for testing a rank choice voting algorithm. Shuffling the array randomly yields candidates that all have pretty much identical vote counts which doesn't mirror most reality (although I will test for that too).
I have this pretty horrible routine which will generate one random array:
const getValues = function () {
const results = [];
const remaining = new Set(alphabet);
const probabilities = [0.6, 0.2, 0.1, 0.1];
for(let i = 0; i < alphabet.length; i++){
const r = Math.random();
const letter = alphabet[i];
if(r < probabilities[i] && remaining.has(letter)){
results.push(letter);
remaining.delete(letter);
}
else{
const rand = Math.floor(Math.random()*remaining.size);
const x = Array.from(remaining)[rand];
remaining.delete(x);
results.push(x);
}
}
return results;
};
this "works" but doesn't quite order things according to the specified probabilities, because of conditional probability. Does someone know of a good way to have the order appear with certain probability, as I described above?
Here is some sample output that I am looking for:
[ [ 'd', 'b', 'a', 'c' ],
[ 'a', 'b', 'c', 'd' ],
[ 'a', 'd', 'b', 'c' ],
[ 'd', 'b', 'a', 'c' ],
[ 'b', 'c', 'a', 'd' ],
[ 'a', 'b', 'c', 'd' ],
[ 'd', 'b', 'c', 'a' ],
[ 'c', 'd', 'a', 'b' ],
[ 'd', 'b', 'a', 'c' ],
[ 'a', 'b', 'c', 'd' ] ]
if you generated enough data it wouldn't fit the desired order/distribution.
You could sort them using a shuffle function like this:
const candidates = [
{ name: "a", weight: 6 },
{ name: "b", weight: 2 },
{ name: "c", weight: 1 },
{ name: "d", weight: 1 }
];
const randomShuffleFn = () => Math.random() - .5;
const shuffleFn = (candidateA, candidateB) =>
Math.random() * (candidateB.weight + candidateA.weight) - candidateA.weight;
console.log([...candidates].sort(randomShuffleFn).sort(shuffleFn));
OK, it's not exactly the same, but I think with tweaking the weights you can get the required distribution (as it is, A wins more than 60% of times).
You could take a random part of the array and normalize the remaining possibilities and take another one until all items are take.
As result, you get nearly a wanted result, as you see in the counts of items and their final index.
const
getIndex = (prob) => prob.findIndex((r => p => r < p || (r -= p, false))(Math.random())),
normalized = array => {
var sum = array.reduce((a, b) => a + b, 0);
return array.map(v => v / sum);
};
var items = ['a', 'b', 'c', 'd'],
probabilities = [0.6, 0.2, 0.1, 0.1],
counts = { a: { 0: 0, 1: 0, 2: 0, 3: 0 }, b: { 0: 0, 1: 0, 2: 0, 3: 0 }, c: { 0: 0, 1: 0, 2: 0, 3: 0 }, d: { 0: 0, 1: 0, 2: 0, 3: 0 } },
l = 100,
index,
result = [],
subP,
subI,
temp;
while (l--) {
temp = [];
subP = probabilities.slice();
subI = items.slice();
while (subP.length) {
sum = subP.reduce
index = getIndex(normalized(subP));
temp.push(subI[index]);
subI.splice(index, 1);
subP.splice(index, 1);
}
result.push(temp);
}
console.log(result.map(a => a.join()));
result.forEach(a => a.forEach((v, i) => counts[v][i]++));
console.log(counts);
.as-console-wrapper { max-height: 100% !important; top: 0; }
This might hopefully help you, example incorporated for your situation from https://github.com/substack/node-deck
Example
const normalize = function (weights) {
if (typeof weights !== 'object' || Array.isArray(weights)) {
throw 'Not an object'
}
let keys = Object.keys(weights);
if (keys.length === 0) return undefined;
let total = keys.reduce(function (sum, key) {
let x = weights[key];
if (x < 0) {
throw new Error('Negative weight encountered at key ' + key);
}
else if (typeof x !== 'number') {
throw new TypeError('Number expected, got ' + typeof x);
}
else {
return sum + x;
}
}, 0);
return total === 1
? weights
: keys.reduce(function (acc, key) {
acc[key] = weights[key] / total;
return acc;
}, {})
;
};
const pick = function (xs) {
if (Array.isArray(xs)) {
return xs[Math.floor(Math.random() * xs.length)];
}
else if (typeof xs === 'object') {
// Weighted Sample
let weights = normalize(xs);
if (!weights) return undefined;
var n = Math.random();
var threshold = 0;
var keys = Object.keys(weights);
for (let i = 0; i < keys.length; i++) {
threshold += weights[keys[i]];
if (n < threshold) return keys[i];
}
throw new Error('Exceeded threshold. Something is very wrong.');
}
else {
throw new TypeError('Must be an Array or an object');
}
};
const shuffle = function (xs) {
if (Array.isArray(xs)) {
let res = xs.slice();
for (var i = res.length - 1; i >= 0; i--) {
var n = Math.floor(Math.random() * i);
var t = res[i];
res[i] = res[n];
res[n] = t;
}
return res;
}
else if (typeof xs === 'object') {
// Weighted
let weights = Object.keys(xs).reduce(function (acc, key) {
acc[key] = xs[key];
return acc;
}, {});
let ret = [];
while (Object.keys(weights).length > 0) {
let key = pick(weights);
delete weights[key];
ret.push(key);
}
return ret;
}
else {
throw new TypeError('Must be an Array or an object');
}
};
let results = [];
for (let i = 0; i < 100; i++) {
let weighted = shuffle({
a : 60,
b : 20,
c : 10,
d : 10, // or .1, 100, 1000
});
results.push(weighted);
}
console.log(results);
I think the problem is poorly stated.
As it is written, A shall be on place 1 with 60% probability, B on place 2 with 20%, C and D on places 3 or 4 with 10% each. There is no distribution which fulfills these probability criteria, so no algorithm can produce it: If in 60% of the cases A is on place 1, either C or D must be on places 3 or 4 in these 60%, so that's way above the required 10% probability.
So, the first task here is to make sense out of what is written in the question (because of course it can make sense, after interpretation).
I guess the 60% for A and 20% for B should not be read as probability but as a kind of popularity. But it cannot be just the quorum for each candidate because in a voting process A will finish on place 1 in 100% of the cases then.
So, let's assume a voting process with some randomness involved which lets A finish on place 1 with 60% probability, B on place 1 (!) with 20% probability, etc. Then we can implement this using a weighted random choice for place 1.
How to continue with the places 2..n? We just keep the weights intact and remove the candidate which already has been chosen. If one of the other candidates has made it to place 1, then this will make A end with a high probability on place 2 which I think makes sense.

Finding every second element in a repeating pattern

Data with repeated 'i's followed by 'i's and/or 't's.
data = ['i','t','t','i','i','t','t','t']
Trying to retrieve the index of the last 't' in the pattern ['i','t','t']:
[2,6] // ['i','t','t','i','i','t','t','t'] # position of the returned 't's
// _______ ^ _______ ^
I'm looking for a non-recursive solution using (pure) functions only, using ramdajs for example.
Tried to use reduce and transduce, but unsuccessful sofar.
One approach would be to use R.aperture to iterate over a 3-element sliding window of the data list, then tracking the position of any sub-list that equals the pattern ['i', 't', 't'].
const data = ['i','t','t','i','i','t','t','t']
const isPattern = R.equals(['i', 't', 't'])
const reduceWithIdx = R.addIndex(R.reduce)
const positions = reduceWithIdx((idxs, next, idx) =>
isPattern(next) ? R.append(idx + 2, idxs) : idxs
, [], R.aperture(3, data))
console.log(positions)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.24.1/ramda.min.js"></script>
A point-free version of this approach could look something like the following, though whether this is preferable comes down to a preference of style/readability.
const data = ['i','t','t','i','i','t','t','t']
const isPattern = R.equals(['i', 't', 't'])
const run = R.pipe(
// create sliding window of 3 elements
R.aperture(3),
// zip sliding window with index
R.chain(R.zip, R.compose(R.range(0), R.length)),
// filter matching pattern
R.filter(R.compose(isPattern, R.nth(1))),
// extract index
R.map(R.compose(R.add(2), R.head))
)
console.log(run(data))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.24.1/ramda.min.js"></script>
You could use a nested approach with a temporary array for checking the same pattern for different starting points. This proposal works with an arbitrary length of pattern and returns the index of the predefined pattern.
This solution features obviously plain Javascript.
index i t i t t i i t t t temp result comment
----- ------------------------------ ------ -------- ------------
0 <i> [0] [] match
1 i <t> [0] [] match
<-> [0] [] no match
2 i t <-> [] [] no match
<i> [2] [] match
3 i <t> [2] [] match
<-> [2] [] no match
4 i t <t> [] [4] pattern found
<-> [] [4] no match
5 <i> [5] [4] match
6 i <-> [] [4] no match
<i> [6] [4] match
7 i <t> [6] [4] match
<-> [6] [4] no match
8 i t <t> [] [4, 8] pattern found
<-> [] [4, 8] no match
9 <-> [] [4, 8] no match
<t> matches 't' at position
<-> does not match at position
function getPatternPos(array, pattern) {
var result = [];
array.reduce(function (r, a, i) {
return r.concat(i).filter(function (j) {
if (i - j === pattern.length - 1 && a === pattern[i - j]) {
result.push(i);
return false;
}
return a === pattern[i - j];
});
}, []);
return result;
}
console.log(getPatternPos(['i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
// [2, 6]
console.log(getPatternPos(['i','t','i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
// [4, 8]
console.log(getPatternPos(['a', 'b', 'a', 'b', 'b', 'a', 'b', 'c', 'd'], ['a', 'b', 'c']));
// [7]
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can do it using Array.prototype.reduce() with a simple condition.
data = ['i','t','t','i','i','t','t','t']
var newData = data.reduce(function (acc, item, index) {
// Check if current element is `t` and the item before it is `i`, `t`
if (item === 't' && data[index - 1] === 'i' && data[index - 2] === 't') {
acc.push(item)
}
return acc;
}, []);
console.log(newData); // ['t', 't']
You can do simply by for loop and check last values of array:
var data = ['i','t','t','i','i','t','t','t'];
var positions = new Array();
for(var i=2; i< data.length; i++){
if(data[i-2] === 'i' && data[i-1] === 't' && data[i] === 't') {
positions.push(i)
}
}
console.log(positions)
data.filter((c, i, d) => c === 't' && d[i - 1] === 't' && d[i - 2] === 'I')
**No negative indexes: **
const matchMaker = () => {
let memo = [‘a’, ‘b’];
return (c, i, d) => {
memo.unshift(c);
return memo[1] + memo[2] + c === 'itt';
}
};
data.filter(matchMaker());
function getPattern(arr, p) {
var r = [],
dir = [];
for (let [i, v] of arr.entries()) {
dir = dir.concat(i).filter(function(x) {
if (v === p[i - x] && i - x === p.length - 1) {
r.push(i);
return false;
}
return v === p[i - x];
})
};
return r;
}
console.log(getPattern(['i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
console.log(getPattern(['i', 't', 'i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
console.log(getPattern(['a', 'b', 'a', 'b', 'b', 'a', 'b', 'c', 'd'], ['a', 'b', 'c']));
.as-console-wrapper { max-height: 100% !important; top: 0; }
In case anyone wants to see an example without using a library that does not use look ahead or behinds ("i + 1" or "i - 2'", etc.).
I think it works similarly to what the Ramda approach does, but I chose to combine the partitioning and equality check in the same loop:
For every step in reduce
Take a section of the array matching the pattern length
Check if it is equal to the pattern
If it is, add the index of the last element in the section to the result of reduce
The code, in which pattern and data are both arrays of strings:
const findPattern = (pattern, data) => data.reduce(
(results, _, i, all) =>
// Check if a slice from this index equals the pattern
arrEqual(all.slice(i, i + pattern.length), pattern)
// Add the last index of the pattern to our results
? (results.push(i + pattern.length - 1), results)
// or, return what we had
: results,
[]);
// Utility method to check array equality
const arrEqual = (arr1, arr2) =>
arr1.length === arr2.length &&
arr1.every((x, i) => x === arr2[i]);
I tested on several data sets and think it meets all requirements:
const findPattern = (pattern, data) => data.reduce(
(results, _, i, all) =>
arrEqual(all.slice(i, i + pattern.length), pattern)
? push(results, i + pattern.length - 1)
: results,
[]);
const arrEqual = (arr1, arr2) =>
arr1.length === arr2.length &&
arr1.every((x, i) => x === arr2[i]);
const push = (xs, x) => (xs.push(x), xs);
// For just string patterns we could also do:
// const arrEqual = (arr1, arr2) => arr1.join("") === arr2.join("");
// Test cases
const dataSets = [
// (i) marks a matching index
// [i] marks a last matching index that should be returned
// | marks a new start
{ pattern: ["i","t","t"], input: ['i','t','t','i','i','t','t','t'], output: [2, 6] },
// |(0) (1) [2]| 3 -(4) (5) [6]| 7
{ pattern: ["i","t"], input: ['i','t','i','t','t','i','i','t','t','t'], output: [1, 3, 7] },
// |(0) [1]|(2) [3]| 4 | 5 |(6) [7]| 8 | 9
{ pattern: ["i","t","t"], input: ['i','t','i','t','t','i','i','t','t','t'], output: [4, 8] },
// |(0) (1)|(2) (3) [4]| 5 |(6) (7) [8]| 9
{ pattern: ["i","t","i"], input: ['i','t','i','t','i','t','i','t','i','t'], output: [2, 4, 6, 8] }
// |(0) (1) [2]| |(6) (7) [8]| 9
// |(2) (3) [4]
// |(4) (5) [6]
];
dataSets.forEach(({pattern, input, output}) =>
console.log(
"| input:", input.join(" "),
"| control:", output.join(", "),
"| answer:", findPattern(pattern, input).join(", ")
)
)
Two years later, lost traveler stumbles upon this question and notices that for variable sized (and especially large pattern with even larger input array or multiple input arrays), classical KMP algorithm would be great.
I think it is worth studing this algorithm.
We will start with simple imperative implementation. Then switch to (at least for me) more intuitive (but probably slightly less optimal, and definitely less optimal when it comes to memory) version with finite automaton. At the end, we'll see something that looks like functional but it is not 100% pure. I wasn't in a mood to torture my self with pure functional implementation of KMP in JS :).
Prefix function KMP, imperative implementation:
function getPatternPos(array, pattern) {
const result = [];
// trying to explain this is a waste of time :)
function createPrefix(pattern) {
// initialize array with zeros
const prefix = Array.apply(null, Array(pattern.length)).map(Number.prototype.valueOf, 0);
let s = 0;
prefix[0] = 0;
for (let i = 1; i < pattern.length; ++i) {
while (s > 0 && pattern[s] !== pattern[i]) {
s = prefix[s - 1];
}
if (pattern[i] === pattern[s]) {
++s;
}
prefix[i] = s;
}
return prefix;
}
const prefix = createPrefix(pattern);
let s = 0;
for (let i = 0; i < array.length; ++i) {
while (s > 0 && pattern[s] !== array[i]) {
s = prefix[s - 1];
}
if (array[i] === pattern[s]) {
++s;
}
if (s === pattern.length) {
result.push(i);
s = 0;
}
}
return result;
}
console.log(getPatternPos(['i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
// [2, 6]
console.log(getPatternPos(['i','t','i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
// [4, 8]
console.log(getPatternPos(['a', 'b', 'a', 'b', 'b', 'a', 'b', 'c', 'd'], ['a', 'b', 'c']));
// [7]
console.log(getPatternPos("ababxabababcxxababc".split(""), "ababc".split("")));
// [11, 18]
console.log(getPatternPos("abababcx".split(""), "ababc".split("")));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Finate automaton KMP implementation:
function getPatternPos(array, pattern) {
const result = [];
function patternCode(i) {
return pattern[i].charCodeAt(0);
}
function createStateMachine(pattern) {
// return single dimensional array as matrix instead of array of arrays,
// for better perfomanse (locality - cache optimizations) and memory usage.
// zero initialize matrix
const sm = Array.apply(null, Array(256 * pattern.length)).map(Number.prototype.valueOf, 0);
let s = 0;
sm[patternCode(0) * pattern.length + 0] = 1;
for (let i = 1; i < pattern.length; ++i) {
// go to same states as if we would go after backing up, so copy all
for (let code = 0; code < 256; ++code)
sm[code * pattern.length + i] = sm[code * pattern.length + s];
// only in case of current symbol go to different/next state
sm[patternCode(i) * pattern.length + i] = i + 1;
// update the state that fallows backup path
s = sm[patternCode(i) * pattern.length + s];
}
return sm;
}
const sm = createStateMachine(pattern);
numStates = pattern.length;
let s = 0;
// now simply fallow state machine
for (let i = 0; i < array.length; ++i) {
s = sm[array[i].charCodeAt(0) * numStates + s];
if (s === pattern.length) {
result.push(i);
s = 0;
}
}
return result;
}
console.log(getPatternPos(['i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
// [2, 6]
console.log(getPatternPos(['i','t','i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
// [4, 8]
console.log(getPatternPos(['a', 'b', 'a', 'b', 'b', 'a', 'b', 'c', 'd'], ['a', 'b', 'c']));
// [7]
console.log(getPatternPos("ababxabababcxxababc".split(""), "ababc".split("")));
// [11, 18]
console.log(getPatternPos("abababcx".split(""), "ababc".split("")));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Funcational-ish KMP implementation:
function getPatternPos(array, pattern) {
// pure function that creates state machine,
// but it's implementation is not complitely pure internally.
function createStateMachine(pattern) {
const initState = Object.create(null);
initState[pattern[0]] = Object.create(initState);
const {currState: finalState} = pattern.slice(1).reduce(function(acc, cval, cidx) {
const newFallbackState = acc.fallbackState[cval] || initState;
// WARNING: non-functional/immutable part,
// to make it complitely pure we would probably need to
// complicate our lives with better data structures or
// lazy evalutaion.
acc.currState[cval] = Object.create(newFallbackState);
return {currState: acc.currState[cval], fallbackState: newFallbackState};
}, {currState: initState[pattern[0]], fallbackState: initState});
return {initState: initState, finalState: finalState};
}
const {initState, finalState} = createStateMachine(pattern);
return array.reduce(function (acc, cval, cidx, array) {
const newState = acc.currState[cval];
if (typeof newState === 'undefined') {
return {currState: initState, result: acc.result};
}
if (newState === finalState) {
// WARNING: not purly functional/immutable,
// still implemenations of JS pure functional/immutable libraries
// probaly use mutation under the hood, and just make it look pure,
// this is what happens here also :)
acc.result.push(cidx);
return {currState: initState, result: acc.result};
}
return {currState: newState, result: acc.result};
}, {currState: initState, result: []}).result;
}
console.log(getPatternPos(['i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
// [2, 6]
console.log(getPatternPos(['i','t','i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
// [4, 8]
console.log(getPatternPos(['a', 'b', 'a', 'b', 'b', 'a', 'b', 'c', 'd'], ['a', 'b', 'c']));
// [7]
console.log(getPatternPos("ababxabababcxxababc".split(""), "ababc".split("")));
// [11, 18]
console.log(getPatternPos("abababcx".split(""), "ababc".split("")));
.as-console-wrapper { max-height: 100% !important; top: 0; }

How can I make a random set generator with equal possibility to generate uniform sets and non-uniform sets?

So let's say I have a set of items:
['a', 'b', 'c', 'd', 'e']
and I want to generate a random set (order does not matter) from those choices:
['a', 'e', 'd', 'c']
which is child's play, but instead of it being unlikely to generate a uniform result:
['c', 'c', 'c', 'c']
compared to something less uniform like:
['a', 'b', 'e', 'd']
I want to make it equally likely that a uniform set can be generated as it is likely that a non-uniform set can be generated.
Edit:
The result I'm trying to express is not just ['c', 'c', 'c', 'c', 'c', 'c'] or ['d', 'd', 'd', 'd', 'd', 'd'] but also the areas in between those uniformities like ['c', 'c', 'c', 'c', 'c', 'a'] or ['d', 'd', 'd', 'd', 'd', 'b'] or ['c', 'c', 'c', 'c', 'b', 'a']. Making all of those uniform sets and the areas in-between equally likely as non-uniform results is what I find challenging to create. I'm at a loss for where to even begin creating a set generator that does that.
Further clarification:
So if I generate a set of 1000 items, I want it to be equally likely that the set is 90% uniform or 100% uniform or 80% uniform or 20% uniform.
How can/should this be done?
From what you're saying, you want to ignore the order of the elements in your random set, so if your original set was ab then the possible outcomes (ignoring order) would be aa, ab, bb, and you'd like to see each of those appearing with equal probability (of 1/3), no?
A brute-force solution to this would be:
generate all outcomes (see Finding All Combinations of JavaScript array values),
sort each of the results so the elements appear alphabetically,
remove duplicates (see Remove duplicates from an array of objects in javascript)
select one of the remaining results at random
So for example, with abc:
all combinations = [`aaa`, `aab`, `aac`
`aba`, `abb`, `abc`
`aca`, `acb`, `acc`
`baa`, `bab`, `bac`
`bba`, `bbb`, `bbc`
`bca`, `bcb`, `bcc`
`caa`, `cab`, `cac`
`cba`, `cbb`, `cbc`
`cca`, `ccb`, `ccc`]
sorted combinations = [`aaa`, `aab`, `aac`
`aab`, `abb`, `abc`
`aac`, `abc`, `acc`
`aab`, `abb`, `abc`
`abb`, `bbb`, `bbc`
`abc`, `bbc`, `bcc`
`aac`, `abc`, `acc`
`abc`, `bbc`, `bcc`
`acc`, `bcc`, `ccc`]
remove duplicates = [`aaa`, `aab`, `aac`,
`abb`, `abc`, `acc`,
`bbb`, `bbc`, `bcc`,
`ccc`]
then choose from these with equal probability of 1/10
EDIT The final output above gives a clue to a non-brute-force solution: for each item the letters that follow each letter are of equal or 'higher' value (alphabetically speaking). So 'b' will never be followed by 'a', and 'c' will never be followed by 'a' or 'b' and so on.
So we can recursively generate all the combinations like this (sorry it's in python, you'll have to translate to javascript):
r=['a','b','c','d']
def get_combos(bound, n):
global r
if n == 1:
return r[bound:]
result=[]
for i in range(bound,len(r)):
for combo in get_combos(i, n-1):
result.append(r[i]+combo)
return result
x = get_combos(0,len(r))
print(x) # ['aaaa', 'aaab', 'aaac', 'aaad', 'aabb', 'aabc', 'aabd', 'aacc', 'aacd', 'aadd', 'abbb', 'abbc', 'abbd', 'abcc', 'abcd', 'abdd', 'accc', 'accd', 'acdd', 'addd', 'bbbb', 'bbbc', 'bbbd', 'bbcc', 'bbcd', 'bbdd', 'bccc', 'bccd', 'bcdd', 'bddd', 'cccc', 'cccd', 'ccdd', 'cddd', 'dddd']
print(len(x)) # 35
This can in fact be done. Just get a random number, and if it is over 0.5 then generate a random set, otherwise generate an extreme set from a random index. See the following code:
function generateRandomOrExtremeSet(a) {
var n = Math.random();
var set = [];
if (n > 0.5)
for (var i = 0; i < a.length; ++i)
set[i] = a[Math.round(Math.random()*(a.length-1))];
else {
var index = Math.round(Math.random() * (a.length-1));
for (var i = 0; i < a.length; ++i) {
if (Math.random() > 0.8) // change to adjust extremeness
set[i] = a[Math.round(Math.random()*(a.length-1))];
else
set[i] = a[index];
}
}
return set;
}
Here's a simple snippet that gets it done, using the strategy of first deciding whether to generate an extreme or non-extreme set.
const choices = ['a', 'b', 'c', 'd', 'e']
const repeatN = (times, x) => {
const ret = [];
while (times--) {
ret.push(x);
}
return ret;
}
const chooseN = (n, choices) => {
let list = choices.slice();
let ret = [];
while (n-- && list.length) {
let i = Math.floor(Math.random() * list.length);
ret.push(list[i]);
}
return ret;
};
const set = Math.random() > 0.5 ?
chooseN(5, choices) :
repeatN(5, chooseN(1, choices)[0]);
console.log(set);
Your original question seems to be stated incorrectly, which is why you are getting so many incorrect responses. In fact, the simple approach will give you the result that you want. You should do this by choosing a random value from your original set, like this:
function randomSet(set, size) {
let result = []
for (let i = 0; i < size; i++) {
// get a random index from the original set
let index = Math.floor(Math.random() * set.length)
result.push(set[index])
}
return result
}
console.log(randomSet(['a', 'b', 'c'], 3))
// These are all equally likely:
// ['a','a','a']
// ['a','b','b']
// ['a','b','c']
// ['c','b','a']
// ['a','b','a']
// ['a','a','b']
// ['b','a','a']
// ['b','a','b']
// ['b','b','b']
// etc.
Alternatively, it's possible that you are misunderstanding the definition of a set. Many of your examples like ['c', 'c', 'c', 'c', 'b', 'a'] are not sets, because they contain repeat characters. Proper sets cannot contain repeat characters and the order of their contents does not matter. If you want to generate a random set from your initial set (in other words, generate a subset), you can do that by picking a size less than or equal to your initial set size, and filling a new set of that size with random elements from the initial set:
function randomSet(set) {
let result = []
let size = Math.floor(Math.random() * set.length)
while(result.length !== size) {
// get a random index from the original set
let index = Math.floor(Math.random() * set.length)
// in this case, we construct the new set simply by removing items from the original set
result.splice(index, 1)
}
return result
}
console.log(randomSet(['a', 'b', 'c']))
// These are all equally likely:
// ['a','b','c']
// ['a','b']
// ['b','c']
// ['a','c']
// ['a']
// ['b']
// ['c']
// no other sets are possible

Finding All Combinations (Cartesian product) of JavaScript array values

How can I produce all of the combinations of the values in N number of JavaScript arrays of variable lengths?
Let's say I have N number of JavaScript arrays, e.g.
var first = ['a', 'b', 'c', 'd'];
var second = ['e'];
var third = ['f', 'g', 'h', 'i', 'j'];
(Three arrays in this example, but its N number of arrays for the problem.)
And I want to output all the combinations of their values, to produce
aef
aeg
aeh
aei
aej
bef
beg
....
dej
EDIT: Here's the version I got working, using ffriend's accepted answer as the basis.
var allArrays = [['a', 'b'], ['c', 'z'], ['d', 'e', 'f']];
function allPossibleCases(arr) {
if (arr.length === 0) {
return [];
}
else if (arr.length ===1){
return arr[0];
}
else {
var result = [];
var allCasesOfRest = allPossibleCases(arr.slice(1)); // recur with the rest of array
for (var c in allCasesOfRest) {
for (var i = 0; i < arr[0].length; i++) {
result.push(arr[0][i] + allCasesOfRest[c]);
}
}
return result;
}
}
var results = allPossibleCases(allArrays);
//outputs ["acd", "bcd", "azd", "bzd", "ace", "bce", "aze", "bze", "acf", "bcf", "azf", "bzf"]
This is not permutations, see permutations definitions from Wikipedia.
But you can achieve this with recursion:
var allArrays = [
['a', 'b'],
['c'],
['d', 'e', 'f']
]
function allPossibleCases(arr) {
if (arr.length == 1) {
return arr[0];
} else {
var result = [];
var allCasesOfRest = allPossibleCases(arr.slice(1)); // recur with the rest of array
for (var i = 0; i < allCasesOfRest.length; i++) {
for (var j = 0; j < arr[0].length; j++) {
result.push(arr[0][j] + allCasesOfRest[i]);
}
}
return result;
}
}
console.log(allPossibleCases(allArrays))
You can also make it with loops, but it will be a bit tricky and will require implementing your own analogue of stack.
I suggest a simple recursive generator function as follows:
// Generate cartesian product of given iterables:
function* cartesian(head, ...tail) {
let remainder = tail.length ? cartesian(...tail) : [[]];
for (let r of remainder) for (let h of head) yield [h, ...r];
}
// Example:
const first = ['a', 'b', 'c', 'd'];
const second = ['e'];
const third = ['f', 'g', 'h', 'i', 'j'];
console.log(...cartesian(first, second, third));
You don't need recursion, or heavily nested loops, or even to generate/store the whole array of permutations in memory.
Since the number of permutations is the product of the lengths of each of the arrays (call this numPerms), you can create a function getPermutation(n) that returns a unique permutation between index 0 and numPerms - 1 by calculating the indices it needs to retrieve its characters from, based on n.
How is this done? If you think of creating permutations on arrays each containing: [0, 1, 2, ... 9] it's very simple... the 245th permutation (n=245) is "245", rather intuitively, or:
arrayHundreds[Math.floor(n / 100) % 10]
+ arrayTens[Math.floor(n / 10) % 10]
+ arrayOnes[Math.floor(n / 1) % 10]
The complication in your problem is that array sizes differ. We can work around this by replacing the n/100, n/10, etc... with other divisors. We can easily pre-calculate an array of divisors for this purpose. In the above example, the divisor of 100 was equal to arrayTens.length * arrayOnes.length. Therefore we can calculate the divisor for a given array to be the product of the lengths of the remaining arrays. The very last array always has a divisor of 1. Also, instead of modding by 10, we mod by the length of the current array.
Example code is below:
var allArrays = [first, second, third, ...];
// Pre-calculate divisors
var divisors = [];
for (var i = allArrays.length - 1; i >= 0; i--) {
divisors[i] = divisors[i + 1] ? divisors[i + 1] * allArrays[i + 1].length : 1;
}
function getPermutation(n) {
var result = "", curArray;
for (var i = 0; i < allArrays.length; i++) {
curArray = allArrays[i];
result += curArray[Math.floor(n / divisors[i]) % curArray.length];
}
return result;
}
Provided answers looks too difficult for me. So my solution is:
var allArrays = new Array(['a', 'b'], ['c', 'z'], ['d', 'e', 'f']);
function getPermutation(array, prefix) {
prefix = prefix || '';
if (!array.length) {
return prefix;
}
var result = array[0].reduce(function(result, value) {
return result.concat(getPermutation(array.slice(1), prefix + value));
}, []);
return result;
}
console.log(getPermutation(allArrays));
You could take a single line approach by generating a cartesian product.
result = items.reduce(
(a, b) => a.reduce(
(r, v) => r.concat(b.map(w => [].concat(v, w))),
[]
)
);
var items = [['a', 'b', 'c', 'd'], ['e'], ['f', 'g', 'h', 'i', 'j']],
result = items.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; }
Copy of le_m's Answer to take Array of Arrays directly:
function *combinations(arrOfArr) {
let [head, ...tail] = arrOfArr
let remainder = tail.length ? combinations(tail) : [[]];
for (let r of remainder) for (let h of head) yield [h, ...r];
}
Hope it saves someone's time.
You can use a typical backtracking:
function cartesianProductConcatenate(arr) {
var data = new Array(arr.length);
return (function* recursive(pos) {
if(pos === arr.length) yield data.join('');
else for(var i=0; i<arr[pos].length; ++i) {
data[pos] = arr[pos][i];
yield* recursive(pos+1);
}
})(0);
}
I used generator functions to avoid allocating all the results simultaneously, but if you want you can
[...cartesianProductConcatenate([['a', 'b'], ['c', 'z'], ['d', 'e', 'f']])];
// ["acd","ace","acf","azd","aze","azf","bcd","bce","bcf","bzd","bze","bzf"]
Easiest way to find the Combinations
const arr1= [ 'a', 'b', 'c', 'd' ];
const arr2= [ '1', '2', '3' ];
const arr3= [ 'x', 'y', ];
const all = [arr1, arr2, arr3];
const output = all.reduce((acc, cu) => {
let ret = [];
acc.map(obj => {
cu.map(obj_1 => {
ret.push(obj + '-' + obj_1)
});
});
return ret;
})
console.log(output);
If you're looking for a flow-compatible function that can handle two dimensional arrays with any item type, you can use the function below.
const getUniqueCombinations = <T>(items : Array<Array<T>>, prepend : Array<T> = []) : Array<Array<T>> => {
if(!items || items.length === 0) return [prepend];
let out = [];
for(let i = 0; i < items[0].length; i++){
out = [...out, ...getUniqueCombinations(items.slice(1), [...prepend, items[0][i]])];
}
return out;
}
A visualisation of the operation:
in:
[
[Obj1, Obj2, Obj3],
[Obj4, Obj5],
[Obj6, Obj7]
]
out:
[
[Obj1, Obj4, Obj6 ],
[Obj1, Obj4, Obj7 ],
[Obj1, Obj5, Obj6 ],
[Obj1, Obj5, Obj7 ],
[Obj2, Obj4, Obj6 ],
[Obj2, Obj4, Obj7 ],
[Obj2, Obj5, Obj6 ],
[Obj2, Obj5, Obj7 ],
[Obj3, Obj4, Obj6 ],
[Obj3, Obj4, Obj7 ],
[Obj3, Obj5, Obj6 ],
[Obj3, Obj5, Obj7 ]
]
You could create a 2D array and reduce it. Then use flatMap to create combinations of strings in the accumulator array and the current array being iterated and concatenate them.
const data = [ ['a', 'b', 'c', 'd'], ['e'], ['f', 'g', 'h', 'i', 'j'] ]
const output = data.reduce((acc, cur) => acc.flatMap(c => cur.map(n => c + n)) )
console.log(output)
2021 version of David Tang's great answer
Also inspired with Neil Mountford's answer
const getAllCombinations = (arraysToCombine) => {
const divisors = [];
let permsCount = 1;
for (let i = arraysToCombine.length - 1; i >= 0; i--) {
divisors[i] = divisors[i + 1] ? divisors[i + 1] * arraysToCombine[i + 1].length : 1;
permsCount *= (arraysToCombine[i].length || 1);
}
const getCombination = (n, arrays, divisors) => arrays.reduce((acc, arr, i) => {
acc.push(arr[Math.floor(n / divisors[i]) % arr.length]);
return acc;
}, []);
const combinations = [];
for (let i = 0; i < permsCount; i++) {
combinations.push(getCombination(i, arraysToCombine, divisors));
}
return combinations;
};
console.log(getAllCombinations([['a', 'b'], ['c', 'z'], ['d', 'e', 'f']]));
Benchmarks: https://jsbench.me/gdkmxhm36d/1
Here's a version adapted from the above couple of answers, that produces the results in the order specified in the OP, and returns strings instead of arrays:
function *cartesianProduct(...arrays) {
if (!arrays.length) yield [];
else {
const [tail, ...head] = arrays.reverse();
const beginning = cartesianProduct(...head.reverse());
for (let b of beginning) for (let t of tail) yield b + t;
}
}
const first = ['a', 'b', 'c', 'd'];
const second = ['e'];
const third = ['f', 'g', 'h', 'i', 'j'];
console.log([...cartesianProduct(first, second, third)])
You could use this function too:
const result = (arrayOfArrays) => arrayOfArrays.reduce((t, i) => { let ac = []; for (const ti of t) { for (const ii of i) { ac.push(ti + '/' + ii) } } return ac })
result([['a', 'b', 'c', 'd'], ['e'], ['f', 'g', 'h', 'i', 'j']])
// which will output [ 'a/e/f', 'a/e/g', 'a/e/h','a/e/i','a/e/j','b/e/f','b/e/g','b/e/h','b/e/i','b/e/j','c/e/f','c/e/g','c/e/h','c/e/i','c/e/j','d/e/f','d/e/g','d/e/h','d/e/i','d/e/j']
Of course you can remove the + '/' in ac.push(ti + '/' + ii) to eliminate the slash from the final result. And you can replace those for (... of ...) with forEach functions (plus respective semicolon before return ac), whatever of those you are more comfortable with.
An array approach without recursion:
const combinations = [['1', '2', '3'], ['4', '5', '6'], ['7', '8']];
let outputCombinations = combinations[0]
combinations.slice(1).forEach(row => {
outputCombinations = outputCombinations.reduce((acc, existing) =>
acc.concat(row.map(item => existing + item))
, []);
});
console.log(outputCombinations);
let arr1 = [`a`, `b`, `c`];
let arr2 = [`p`, `q`, `r`];
let arr3 = [`x`, `y`, `z`];
let result = [];
arr1.forEach(e1 => {
arr2.forEach(e2 => {
arr3.forEach(e3 => {
result[result.length] = e1 + e2 + e3;
});
});
});
console.log(result);
/*
output:
[
'apx', 'apy', 'apz', 'aqx',
'aqy', 'aqz', 'arx', 'ary',
'arz', 'bpx', 'bpy', 'bpz',
'bqx', 'bqy', 'bqz', 'brx',
'bry', 'brz', 'cpx', 'cpy',
'cpz', 'cqx', 'cqy', 'cqz',
'crx', 'cry', 'crz'
]
*/
A solution without recursion, which also includes a function to retrieve a single combination by its id:
function getCombination(data, i) {
return data.map(group => {
let choice = group[i % group.length]
i = (i / group.length) | 0;
return choice;
});
}
function* combinations(data) {
let count = data.reduce((sum, {length}) => sum * length, 1);
for (let i = 0; i < count; i++) {
yield getCombination(data, i);
}
}
let data = [['a', 'b', 'c', 'd'], ['e'], ['f', 'g', 'h', 'i', 'j']];
for (let combination of combinations(data)) {
console.log(...combination);
}

Categories