Rearrange / transpose array items by keys diagonally in JavaScript - javascript

I have an array of strings, for example:
var arr=["dog", "cat", "bear", "wolf", "lynx", "hare", "sheep", "owl", "hen"];
To refer to any of these values, there are corresponding keys from 0 to 8, i.e. the arr[3] corresponds to "wolf". The amount of items of the actual array may vary and have more than 100 items in it. In this example there are 9 [0,1,2,3,4,5,6,7,8].
What I would like to accomplish is to rearrange the items by their keys diagonally, i.e. from:
[0,1,2,
3,4,5,
6,7,8]
into:
[0,2,5,
1,4,7,
3,6,8]
i.e. into [0,2,5,1,4,7,3,6,8], and thus also the sequence of the corresponding values from the original:
var arr=["dog", "cat", "bear", "wolf", "lynx", "hare", "sheep", "owl", "hen"];
resulting into the rearranged values:
var arr2=["dog", "bear", "hare", "cat", "lynx", "wolf", "owl", "sheep", "hen"];
The use of this solution would be implemented in more complex visualization of string items (strings each consisting of binary digits that correspond to UTF-8 encoded values of another data) in square shape, arranging them diagonally specifically from the left top corner. Thank you in advance!

It took some time for me to get the math right, but I was able to make a function which returns an Array of indexes in the correct order:
function getDiagonalArrayIndexes(length) {
const sqrt = Math.floor(Math.sqrt(length));
const formula = (x, y) => (y + x) * (y + x + 1) / 2 + x;
return Array.from({ length: sqrt*sqrt }, (_, i) => {
let x = i % sqrt, y = Math.floor(i / sqrt);
if (x + y < sqrt) {
return formula(x, y);
} else {
return length - 1 - formula(sqrt - 1 - x, sqrt - 1 - y);
}
})
// In case length's square root is not an integer
.concat(new Array(length - sqrt * sqrt).fill(null));
}
printSquare( getDiagonalArrayIndexes(9) );
printSquare( getDiagonalArrayIndexes(16) );
printSquare( getDiagonalArrayIndexes(25) ); /* Just for the demo */ function printSquare(n){const o=Math.sqrt(n.length),t=[];for(var e=0,a=0;e<n.length;e++)e>=o&&e%o==0&&a++,t[a]=t[a]||[],t[a].push(n[e]);console.log("[\n"+t.map(n=>n.map(n=>(" "+n).slice(-3)).join(",")).join(",\n")+"\n]")}document.body.innerHTML="<style>\n .as-console-wrapper { max-height: 100% !important; top: 0; }\n</style>";
You can then reuse it and map the indexes using your data:
function reorderDiagonally(arr) {
return getDiagonalArrayIndexes(arr.length)
.map(i => i !== null ? arr[i] : '');
}
var arr = ["dog", "cat", "bear", "wolf", "lynx", "hare", "sheep", "owl", "hen"];
console.log(JSON.stringify( reorderDiagonally(arr) )); /* Just for the demo */ function getDiagonalArrayIndexes(r){const t=Math.floor(Math.sqrt(r)),n=(r,t)=>(t+r)*(t+r+1)/2+r,l=Array.from({length:t*t},(l,a)=>{let e=a%t,o=Math.floor(a/t);return e+o<t?n(e,o):r-1-n(t-1-e,t-1-o)});return l.concat(new Array(r-l.length).fill(null))}

path method, gives valid diagonal path for given [row, col]
diagonals, aggregate paths for starting on first column and last row.
Simple map to shuffle based on the diagonal paths generated.
PS: Not tested the cases where array length is not perfect square.
const path = (row, col, len, res) => {
while (row > -1 && col < len) {
res.push([row, col]);
row--;
col++;
}
return res;
};
const diagonals = (len) => {
const res = [];
for (let i = 0; i < len; i++) {
path(i, 0, len, res);
}
for (let j = 1; j < len; j++) {
path(len - 1, j, len, res);
}
return res;
};
// const input = [0, 1, 2, 3, 4, 5, 6, 7, 8];
const input = ["dog", "cat", "bear", "wolf", "lynx", "hare", "sheep", "owl", "hen"]
const len = Math.floor(Math.sqrt(input.length));
const output = [...input]
diagonals(len).map(([row, col], i) => output[row * len + col] = input[i]);
console.log(output.join(', '));

Related

Generate a new uniformly distributed array from the elements of the previous array

It is basically a constraint optimisation problem where I need to find similar colors given a particular color, from a color database.
Consider an array (signifying the distance between the inputColor and the allColorsInDB): [0, 5, 6, 1, 0.56, 4350, 64, 345, 20, 14, 2.045, 54, 56]
Construct a new array of limit N (user input) with the elements of the previous array such that:
K is the middle element of the new array. (K is user input and is already present in the original array)
Each interval between i th and (i+1)th element in the new array is same or closest possible.
Constraints:
N <= originalArray.length
K is always present in the original Array
This is what I'm trying...
const convert = require('#csstools/convert-colors');
export type Lab = [number, number, number];
const calculateEuclideanDistance = (color1: Lab, color2: Lab) => {
const [l1, a1, b1] = color1;
const [l2, a2, b2] = color2;
return Math.sqrt((l2 - l1) ** 2 + (a2 - a1) ** 2 + (b2 - b1) ** 2);
};
const getSimilarColors = (brand: keyof typeof colors.brands, color: string, threshold?: number, limit?: number) => {
const brandColors = colors.brands[brand].colors;
const colorLab = convert.hex2lab(color);
let allColors = Object.keys(brandColors)
.map((curr) => {
// #ts-ignore
const currLab = brandColors[curr as keyof typeof brandColors].lab as Lab;
return {
...brandColors[curr as keyof typeof brandColors],
hex: curr,
distance: calculateEuclideanDistance(colorLab, currLab),
};
})
// Sorting colors from dark to light
.sort((a, b) => a.lab[0]! - b.lab[0]!);
if (threshold) {
allColors = allColors.filter((c) => c.distance <= threshold);
}
if (!limit || limit >= allColors.length) {
return allColors;
}
// TODO: Improve this logic, right now we're assuming limit is always 5, hence computing it statically
const filteredColors = new Array(5);
const midIndex = allColors.findIndex((c) => c.hex === color)!;
// eslint-disable-next-line prefer-destructuring
filteredColors[0] = allColors[0];
filteredColors[2] = allColors[midIndex];
filteredColors[4] = allColors[allColors.length - 1];
filteredColors[1] = allColors[Math.floor(midIndex / 2)];
filteredColors[3] = allColors[Math.floor((allColors.length - 1 + midIndex) / 2)];
return filteredColors;
};
Result that I'm expecting:
Sorted Colors Based on LAB
Sorted Colors Based on LAB
The thing that I'm getting from the API for now is correct, however the I'm doing it statically.
Input
Output
Formatted Output

How to find all no repeat combinations form specific single arrays use Javascript

I am a Javascript beginner, I have a personal project to use program to find all the possible & no repeat combinations form specific arrays
I suppose have 3 sets of products and 10 style in each set, like this array
[1,2,3,4..10,1,2,4...8,9,10]
①②③④⑤⑥⑦⑧⑨⑩
①②③④⑤⑥⑦⑧⑨⑩
①②③④⑤⑥⑦⑧⑨⑩
totaly array length = 30
I plan to randomly separate it into 5 children, but they can't repeat the same products style
OK result:
①②③④⑤⑥ ✔
②③④⑤⑥⑦ ✔
①②③⑧⑨⑩ ✔
④⑥⑦⑧⑨⑩ ✔
①⑤⑦⑧⑨⑩ ✔
Everyone can evenly assign products that are not duplicated
NG:
①②③④⑤⑥ ✔
①②③④⑤⑦ ✔
①②⑥⑧⑨⑩ ✔
③④⑤⑦⑧⑨ ✔
⑥⑦⑧⑨⑩⑩ ✘ (because number 10 repeated)
My solution is randomly to assign 5 sets of arrays, then use "new Set(myArray[i]).size;" check the value sum is 30 or not, Use [do..white], while sum is not 30 then repeat to do the random assign function until the result is not duplicated.
like this:
function splitArray() {
do {
var productArray = [1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10]; //Just for sample, actualy this array will be more then 30
var productPerChildArray = [];
for (var i = 0; i < 5; i++) {
GroupNum = [];
for (var v = 0; v < 6; v++) {
var selectNum = productArray[Math.floor(Math.random() * productArray.length)];
GroupNum.push(selectNum);
productArray = removeItemOnce(productArray, selectNum);
}
productPerChildArray.push(GroupNum);
}
} while (checkIfArrayIsUnique(productPerChildArray));
return productPerChildArray;
}
//---------check repeat or not----------
function checkIfArrayIsUnique(myArray) {
var countRight = 0;
for (var i = 0; i < myArray.length; i++) {
countRight += new Set(myArray[i]).size;
}
return (countRight != 5*6);
}
//----------update productArray status----------
function removeItemOnce(arr, value) {
var index = arr.indexOf(value);
if (index > -1) {
arr.splice(index, 1);
}
return arr;
}
console.log(splitArray());
Seems to solve the problem, but actualy productArray is not must 30, this solution will spend to much time to try the no repeat combination. Low efficiency
I believe they have a other solution to solve the problem better than my idea
Any help would be greatly appreciated.
My approach would be: just place the next number in an array that is selected randomly - of course, filter out those that already contain the next number.
// the beginning dataset
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// the size of the groups to be formed (must divide the
// size of beginning dataset without a remainder)
const ARR_LENGTH = 6
// creating the array that will be filled randomly
const getBaseArray = ({ data, arrLength }) => {
const num = parseInt(data.length / arrLength, 10)
return Array.from(Array(num), () => [])
}
// filter arrays that satisfy conditions
const conditions = ({ subArr, val }) => {
if (subArr.includes(val)) return false
if (ARR_LENGTH <= subArr.length) return false
return true
}
const getArraysFiltered = ({ val, arr }) => {
return arr
.map((e, i) => ({
origIdx: i,
subArr: e,
}))
.filter(({ subArr }) => conditions({ subArr, val }))
}
// select a random array from a list of arrays
const getRandomArrIdx = ({ arr }) => {
return Math.floor(Math.random() * arr.length)
}
// get the original array index from the filtered values
const getArrIdx = ({ val, arr }) => {
const filtered = getArraysFiltered({ val, arr })
if (!filtered.length) return -1
const randomArrIdx = getRandomArrIdx({ arr: filtered })
return filtered[randomArrIdx].origIdx
}
const getFinalArr = ({ data }) => {
// short circuit: if the data cannot be placed in
// arrays evenly, then it's a mistake (based on
// the current ruleset)
if (data.length % ARR_LENGTH) return [false]
// creating the array that will hold the numbers
const arr = getBaseArray({ data, arrLength: ARR_LENGTH })
let i = 0;
for (i; i < data.length; i++) {
const idx = getArrIdx({
val: data[i],
arr,
})
// if there's no place that the next number could be
// placed, then break (prematurely), so the algorithm
// can be restarted as early as possible
if (idx === -1) break;
arr[idx].push(data[i])
}
if (i < data.length) {
// restart algorithm if we couldn't place
// all the numbers in the dataset
return getFinalArr({ data })
} else {
return arr
}
}
// constructing the final array of arrays & logging them:
console.log('res', getFinalArr({ data }).join(' '))
console.log('res', getFinalArr({ data }).join(' '))
console.log('res', getFinalArr({ data }).join(' '))
console.log('res', getFinalArr({ data }).join(' '))
I don't know if it's more efficient, but I found this question interesting.
Now, the algorithm is
broken down into small logical steps that are
easy to follow
simple to tweak to needs
works with all sizes of data (if the groups to be made can be filled equally)

Find most similar array of numbers

Let's say I have two arrays:
a=[168, 76, 62, 86]
b=[168, 80, 65, 90]
My input
[166.5, 75.5, 62, 86]
Now I want to get array "a" as my "result" because it is more similar to "a" than it is to "b".
How can I do that?
You could collect the absolute deltas and choose the one with the smaller error.
var array1 = [168, 76, 62, 86],
array2 = [168, 80, 65, 90],
input = [166.5, 75.5, 62, 86],
error = [array1, array2].map(function (a) {
return input.reduce(function (r, b, i) {
return r + Math.abs(a[i] -b);
}, 0);
});
console.log(error); // [2, 13] take the first one with smaller error.
Let's say that you've got two number arrays
x = [x1, x2, ..., xn]
y = [y1, y2, ..., yn]
The difference (i.e inverse similarity) between them is calculated by passing them through some error function E let's say
E = Σ (xi - yi)2
it's the same as calculating:
(x1 - y2)2 + ... + (xn - yn)2
= Σx2 + Σy2 - Σ(2xiyi)
= Σxi2 + Σyi2 - 2ΣxiΣyi
which means we can now use just the built-in Javascript methods .map() and .reduce().
const toSquare(ar) = ar.map( v => v*v )
const sum(ar) = ar.reduce( (acc, v) => acc + v )
function error(ar1, ar2) {
return sum(toSquare(ar1)) + sum(toSquare(ar2)) + 2*sum(ar1)*sum(ar2)
}
Much simpler:
you can also use underscore's or lodash's .zip function:
function error(ar1, ar2) {
return _.zip([ar1, ar2]).map( pair => Math.pow(p[0]-p[1], 2) ).reduce( (acc, v) => acc + v )
}
You will have to create a manual function to do that.
There is no built in way to do so.
function closest( inputArray, controlArrays) {
var margins;
for(var i=0, iMax=controlArrays.length; i < iMax; i++ ){
margins[i] = 0;
for(var j=0, jMax=inputArray.length; j < jMax; j++) {
//get the difference between the numbers and add it for the margins for that control array
margins[i] += Math.abs(inputArray[j]-controlArrays[j][i]);
}
}
//find smallest margin
var index = 0;
var value = margins[0];
for (var m = 1, mMax = temp.length; m < mMax; m++) {
if (temp[m] < value) {
value = temp[m];
index = m;
}
}
//return the smalles margin;
return controlArrays[index];
}

Implementation of the majority merge algorithm for finding a super-sequence from sub-sequences?

I've got a small set of sequences of unique values that I want to combine into a single super-sequence where the relative order of each value is maintained, to the extent possible. For instance (quotes around strings ignored for simplicity):
list1 = [Mary, Bob, Sue, Roger]
list2 = [Bob, Alice, Sue, Dave]
list3 = [Mary, Bob, Larry, Sue, Roger]
superSequence = [Mary, Bob, Alice, Larry, Sue, Roger, Dave]
The goal is to generate an object from which the original lists can be recreated, such as:
obj = {
Mary: [1, 3],
Bob: [1, 3],
Alice: [2],
Larry: [3],
Sue: [1, 2, 3],
Roger: [1, 3],
Dave: [2]
}
Ignoring for the moment that object key order isn't guaranteed in all cases, one could iterate over these keys and use the indices in each associated array to regenerate the original lists. Because the super-sequence is represented as a JS object, the values in it must be unique.
Clearly, not every set of sequences can be combined in this way:
list1 = [Mary, Bob, Sue]
list2 = [Bob, Mary, Sue]
superSequence = [Mary, Bob, Sue] OR
superSequence = [Bob, Mary, Sue]
Picking one or the other should be fine in most cases, with bonus points for an algorithm that can highlight where the order was indeterminate.
In researching this, I seem to have stumbled upon an NP-hard problem that's well-studied in compression, bioinformatics, and other fields. There's something called a majority merge algorithm that seems to be a reasonably decent approximation for this problem, but my ability to translate academic paper pseudo-code into something usable has atrophied over the years. So I was hoping to find an actual implementation in JS, or C, or Python or something that doesn't rely on magic libraries.
So after some further thought, I realized there is a much simpler answer to your question. Since we can assume the same name does not occur multiple times in a given list this will work:
var simpleCombine = function (arr1, arr2) {
"use strict";
arr1 = JSON.parse(JSON.stringify(arr1));
arr2 = JSON.parse(JSON.stringify(arr2));
var i, j, arrOut = [];
while (arr1.length) {
var val = arr1.shift(), found = false;
for (j = 0; j < arr2.length; j += 1) {
if (val === arr2[j]) {
//If we wound an overlap then put everything to the left of it
// in arr2 into the final array
found = true;
var pos = arrOut.length;
arrOut.push(val);
var newVals = arr2.splice(0, j);
while (newVals.length) {
arrOut.splice(pos, 0, newVals.pop());
}
arr2.shift(); //get rid of dup
break;
}
}
if (!found) {
//No overlap found, just add it to the out array
arrOut.push(val);
}
}
//anything left in arr2? Add it to out array
arrOut = arrOut.concat(arr2);
//check for duplicates based on user requirement of each item in the
// sequence only occurs once.
for (i = 0; i < arrOut.length; i += 1) {
for (j = i + 1; j < arrOut.length; j += 1) {
if (arrOut[i] === arrOut[j]) {
//If we find an overlap warn the user, and remove the dup.
console.warn('Even with strict ordering, multiple solutions are possible');
arrOut.splice(i,1);
i -= 1;
break;
}
}
}
return arrOut;
};
var findMultipleSCS = function (arr) {
var first = arr.shift();
while (arr.length) {
first = simpleCombine(first, arr.shift());
}
return first;
};
list1 = ["Mary", "Bob", "Sue", "Roger"];
list2 = ["Bob", "Alice", "Sue", "Dave"];
list3 = ["Mary", "Bob", "Larry", "Sue", "Roger"];
console.log(findMultipleSCS([list1, list2, list3]));
My original answer is below because it is more accurate for lists that may contain the same name multiple times.
//This code works for things where the important thing is that order is
//maintained, not that each entry only occurs once
var findSCS = (function () {
'use strict';
var lcsLen, lcsBack, combine;
lcsLen = function(arr1, arr2) {
//This function moves through the arrays developing the
// length of the longest possible sequence of identical order.
var dists = [[0]], i, j;
for (i = 0; i < arr1.length; i += 1) {
dists[i + 1] = [];
for (j = 0; j < arr2.length; j += 1) {
dists[i + 1][0] = 0; // initialize 0'th column/row with 0
dists[0][j + 1] = 0; // this could be done in a separate loop
dists[i + 1][j + 1] = dists[i + 1][j + 1] || 0; // initialize i,j
if (arr1[i] === arr2[j]) {
//if this condition is met then we have a longer overlap
dists[i + 1][j + 1] = dists[i][j] + 1;
} else {
//if not take the max length so far
dists[i + 1][j + 1] = Math.max(dists[i][j + 1], dists[i + 1][j]);
}
}
}
return dists;
};
lcsBack = function (dists, x, y, i, j) {
//this recursive function takes the longest possible array and build
// the actual list starting from the bottom right of the matrix
// created by lcsLen
if (!i || !j) {
return [];
} else if(x[i - 1] === y[j - 1]) {
return lcsBack(dists, x, y, i - 1, j - 1).concat([x[i - 1]]);
} else {
if (dists[i][j-1] > dists[i-1][j]) {
return lcsBack(dists, x, y, i, j-1);
} else {
return lcsBack(dists,x,y,i-1,j);
}
}
};
combine = function (lcs, arr1, arr2) {
//this take the lcs and fills in the non-overlapping part of
// the original lists, creating the scs
var out = JSON.parse(JSON.stringify(arr1));
var i, testing = 0, outPos = 0, positions = [0];
for (i = 0; i < arr1.length && testing < lcs.length; i += 1) {
if (lcs[testing] === arr1[i]) {
positions[testing + 1] = i;
testing += 1;
}
}
testing = 0; outPos = 0;
for (i = 0; i < arr2.length; i += 1) {
if (lcs[testing] === undefined || lcs[testing] !== arr2[i]) {
out.splice(positions[testing] + outPos, 0, arr2[i]);
outPos += 1;
} else {
testing += 1;
outPos += 1;
}
}
return out;
};
return function (arr1, arr2) {
//get the length matrix to determine the maximum sequence overlap
var lcsLenMat = lcsLen(arr1,arr2);
//Take that distance matrix and build the actual sequence (recursively)
var lcs = lcsBack(lcsLenMat, arr1, arr2, arr1.length, arr2.length);
//Build the SCS
var tempScs = combine(lcs, arr1, arr2);
//This code will allow for duplicates, and in your second example
// It will generate a list with two bobs, which is arguably more
// correct for general purpose use.
return tempScs;
}());
var findMultipleSCS = function (arr) {
var first = arr.shift();
while (arr.length) {
first = findSCS(first, arr.shift());
}
return first;
};
list1 = ["Mary", "Bob", "Sue", "Roger"];
list2 = ["Bob", "Alice", "Sue", "Dave"];
list3 = ["Mary", "Bob", "Larry", "Sue", "Roger"];
console.log(findMultipleSCS([list1, list2, list3]));
Most of these ideas where taken from https://en.wikipedia.org/wiki/Longest_common_subsequence_problem and https://en.wikipedia.org/wiki/Shortest_common_supersequence_problem
The order you put these in will determine which non unique solution you get. For instance list1, list2, list3 give you your first answer, however list2, list3, list1 gives you the also correct:
["Mary", "Bob", "Larry", "Alice", "Sue", "Dave", "Roger"]
If you want to maintain the priority order than list1, list2, list3 does have a unique solution and this will alert you of a duplicate possibility with a console.warn looking for duplicates.
Building on the simpleCombine() function from aduss I came up with a solution that seems to work pretty well. It doesn't currently flag that duplicate items in the result are getting deleted, but that could be implemented with some additional logic in the final filter() call.
function combineLists(...lists)
{
var superSequence = lists.slice(1).reduce((list1, list2) => {
var result = [];
// we need to make a copy of list2 since we mutate it in the loop below
list2 = [].concat(list2);
list1.forEach(item => {
var overlapIndex = list2.indexOf(item);
if (overlapIndex > -1) {
// add 1 to overlapIndex so we also splice out the matching item
result = result.concat(list2.splice(0, overlapIndex + 1));
} else {
result.push(item);
}
});
// anything remaining in list2 is by definition not in list1, so add
// those items to the result
return result.concat(list2);
}, lists[0]);
// look back at the list up to the current item and then filter it out if
// there's a duplicate found. this keeps the first instance of each item.
return superSequence.filter((item, i, list) => list.slice(0, i).indexOf(item) == -1);
}
var list1 = ["Mary", "Bob", "Sue", "Roger"],
list2 = ["Bob", "Alice", "Jimmy", "Chuck", "Sue", "Dave"],
list3 = ["Mary", "Bob", "Larry", "Sue", "Roger"];
console.log(combineLists(list1, list2, list3).join(" "));

Javascript - Generating all combinations of elements in a single array (in pairs)

I've seen several similar questions about how to generate all possible combinations of elements in an array. But I'm having a very hard time figuring out how to write an algorithm that will only output combination pairs. Any suggestions would be super appreciated!
Starting with the following array (with N elements):
var array = ["apple", "banana", "lemon", "mango"];
And getting the following result:
var result = [
"apple banana"
"apple lemon"
"apple mango"
"banana lemon"
"banana mango"
"lemon mango"
];
I was trying out the following approach but this results in all possible combinations, instead only combination pairs.
var letters = splSentences;
var combi = [];
var temp= "";
var letLen = Math.pow(2, letters.length);
for (var i = 0; i < letLen ; i++){
temp= "";
for (var j=0;j<letters.length;j++) {
if ((i & Math.pow(2,j))){
temp += letters[j]+ " "
}
}
if (temp !== "") {
combi.push(temp);
}
}
Here are some functional programming solutions:
Using EcmaScript2019's flatMap:
var array = ["apple", "banana", "lemon", "mango"];
var result = array.flatMap(
(v, i) => array.slice(i+1).map( w => v + ' ' + w )
);
console.log(result);
Before the introduction of flatMap (my answer in 2017), you would go for reduce or [].concat(...) in order to flatten the array:
var array = ["apple", "banana", "lemon", "mango"];
var result = array.reduce( (acc, v, i) =>
acc.concat(array.slice(i+1).map( w => v + ' ' + w )),
[]);
console.log(result);
Or:
var array = ["apple", "banana", "lemon", "mango"];
var result = [].concat(...array.map(
(v, i) => array.slice(i+1).map( w => v + ' ' + w ))
);
console.log(result);
A simple way would be to do a double for loop over the array where you skip the first i elements in the second loop.
let array = ["apple", "banana", "lemon", "mango"];
let results = [];
// Since you only want pairs, there's no reason
// to iterate over the last element directly
for (let i = 0; i < array.length - 1; i++) {
// This is where you'll capture that last value
for (let j = i + 1; j < array.length; j++) {
results.push(`${array[i]} ${array[j]}`);
}
}
console.log(results);
Rewritten with ES5:
var array = ["apple", "banana", "lemon", "mango"];
var results = [];
// Since you only want pairs, there's no reason
// to iterate over the last element directly
for (var i = 0; i < array.length - 1; i++) {
// This is where you'll capture that last value
for (var j = i + 1; j < array.length; j++) {
results.push(array[i] + ' ' + array[j]);
}
}
console.log(results);
In my case, I wanted to get the combinations as follows, based on the size range of the array:
function getCombinations(valuesArray: String[])
{
var combi = [];
var temp = [];
var slent = Math.pow(2, valuesArray.length);
for (var i = 0; i < slent; i++)
{
temp = [];
for (var j = 0; j < valuesArray.length; j++)
{
if ((i & Math.pow(2, j)))
{
temp.push(valuesArray[j]);
}
}
if (temp.length > 0)
{
combi.push(temp);
}
}
combi.sort((a, b) => a.length - b.length);
console.log(combi.join("\n"));
return combi;
}
Example:
// variable "results" stores an array with arrays string type
let results = getCombinations(['apple', 'banana', 'lemon', ',mango']);
Output in console:
The function is based on the logic of the following documentation, more information in the following reference:
https://www.w3resource.com/javascript-exercises/javascript-function-exercise-3.php
if ((i & Math.pow(2, j)))
Each bit of the first value is compared with the second, it is taken as valid if it matches, otherwise it returns zero and the condition is not met.
Although solutions have been found, I post here an algorithm for general case to find all combinations size n of m (m>n) elements. In your case, we have n=2 and m=4.
const result = [];
result.length = 2; //n=2
function combine(input, len, start) {
if(len === 0) {
console.log( result.join(" ") ); //process here the result
return;
}
for (let i = start; i <= input.length - len; i++) {
result[result.length - len] = input[i];
combine(input, len-1, i+1 );
}
}
const array = ["apple", "banana", "lemon", "mango"];
combine( array, result.length, 0);
I ended up writing a general solution to this problem, which is functionally equivalent to nhnghia's answer, but I'm sharing it here as I think it's easier to read/follow and is also full of comments describing the algorithm.
/**
* Generate all combinations of an array.
* #param {Array} sourceArray - Array of input elements.
* #param {number} comboLength - Desired length of combinations.
* #return {Array} Array of combination arrays.
*/
function generateCombinations(sourceArray, comboLength) {
const sourceLength = sourceArray.length;
if (comboLength > sourceLength) return [];
const combos = []; // Stores valid combinations as they are generated.
// Accepts a partial combination, an index into sourceArray,
// and the number of elements required to be added to create a full-length combination.
// Called recursively to build combinations, adding subsequent elements at each call depth.
const makeNextCombos = (workingCombo, currentIndex, remainingCount) => {
const oneAwayFromComboLength = remainingCount == 1;
// For each element that remaines to be added to the working combination.
for (let sourceIndex = currentIndex; sourceIndex < sourceLength; sourceIndex++) {
// Get next (possibly partial) combination.
const next = [ ...workingCombo, sourceArray[sourceIndex] ];
if (oneAwayFromComboLength) {
// Combo of right length found, save it.
combos.push(next);
}
else {
// Otherwise go deeper to add more elements to the current partial combination.
makeNextCombos(next, sourceIndex + 1, remainingCount - 1);
}
}
}
makeNextCombos([], 0, comboLength);
return combos;
}
The best solutions I have found - https://lowrey.me/es6-javascript-combination-generator/
Uses ES6 generator functions, I adapted to TS. Most often you don't need all of the combinations at the same time. And I was getting annoyed by writing loops like for (let i=0; ... for let (j=i+1; ... for (let k=j+1... just to get combos one by one to test if I need to terminate the loops..
export function* combinations<T>(array: T[], length: number): IterableIterator<T[]> {
for (let i = 0; i < array.length; i++) {
if (length === 1) {
yield [array[i]];
} else {
const remaining = combinations(array.slice(i + 1, array.length), length - 1);
for (let next of remaining) {
yield [array[i], ...next];
}
}
}
}
usage:
for (const combo of combinations([1,2,3], 2)) {
console.log(combo)
}
output:
> (2) [1, 2]
> (2) [1, 3]
> (2) [2, 3]
Just to give an option for next who'll search it
const arr = ['a', 'b', 'c']
const combinations = ([head, ...tail]) => tail.length > 0 ? [...tail.map(tailValue => [head, tailValue]), ...combinations(tail)] : []
console.log(combinations(arr)) //[ [ 'a', 'b' ], [ 'a', 'c' ], [ 'b', 'c' ] ]
There are also this answer:
https://stackoverflow.com/a/64414875/19518308
The alghorithm is this answer generates all the possible sets of combination(or choose(n, k)) of n items within k spaces.
The algorhitm:
function choose(arr, k, prefix=[]) {
if (k == 0) return [prefix];
return arr.flatMap((v, i) =>
choose(arr.slice(i+1), k-1, [...prefix, v])
);
}
console.log(choose([0,1,2,3,4], 3));
I had a similar problem and this algorhitm is working very well for me.
Using map and flatMap the following can be done (flatMap is only supported on chrome and firefox)
var array = ["apple", "banana", "lemon", "mango"]
array.flatMap(x => array.map(y => x !== y ? x + ' ' + y : null)).filter(x => x)
I think it is an answer to all such questions.
/**
*
* Generates all combination of given Array or number
*
* #param {Array | number} item - Item accepts array or number. If it is array exports all combination of items. If it is a number export all combination of the number
* #param {number} n - pow of the item, if given value is `n` it will be export max `n` item combination
* #param {boolean} filter - if it is true it will just export items which have got n items length. Otherwise export all posible length.
* #return {Array} Array of combination arrays.
*
* Usage Example:
*
* console.log(combination(['A', 'B', 'C', 'D'], 2, true)); // [[ 'A','A' ], [ 'A', 'B' ]...] (16 items)
* console.log(combination(['A', 'B', 'C', 'D'])); // [['A', 'A', 'A', 'B' ],.....,['A'],] (340 items)
* console.log(comination(4, 2)); // all posible values [[ 0 ], [ 1 ], [ 2 ], [ 3 ], [ 0, 0 ], [ 0, 1 ], [ 0, 2 ]...] (20 items)
*/
function combination(item, n) {
const filter = typeof n !=='undefined';
n = n ? n : item.length;
const result = [];
const isArray = item.constructor.name === 'Array';
const count = isArray ? item.length : item;
const pow = (x, n, m = []) => {
if (n > 0) {
for (var i = 0; i < count; i++) {
const value = pow(x, n - 1, [...m, isArray ? item[i] : i]);
result.push(value);
}
}
return m;
}
pow(isArray ? item.length : item, n);
return filter ? result.filter(item => item.length == n) : result;
}
console.log("#####first sample: ", combination(['A', 'B', 'C', 'D'], 2)); // with filter
console.log("#####second sample: ", combination(['A', 'B', 'C', 'D'])); // without filter
console.log("#####third sample: ", combination(4, 2)); // gives array with index number
Generating combinations of elements in an array is a lot like counting in a numeral system,
where the base is the number of elements in your array (if you account for the leading zeros that will be missing).
This gives you all the indices to your array (concatenated):
arr = ["apple", "banana", "lemon", "mango"]
base = arr.length
idx = [...Array(Math.pow(base, base)).keys()].map(x => x.toString(base))
You are only interested in pairs of two, so restrict the range accordingly:
range = (from, to) = [...Array(to).keys()].map(el => el + from)
indices = range => range.map(x => x.toString(base).padStart(2,"0"))
indices( range( 0, Math.pow(base, 2))) // range starts at 0, single digits are zero-padded.
Now what's left to do is map indices to values.
As you don't want elements paired with themselves and order doesn't matter,
those need to be removed, before mapping to the final result.
const range = (from, to) => [...Array(to).keys()].map(el => el + from)
const combinations = arr => {
const base = arr.length
return range(0, Math.pow(base, 2))
.map(x => x.toString(base).padStart(2, "0"))
.filter(i => !i.match(/(\d)\1/) && i === i.split('').sort().join(''))
.map(i => arr[i[0]] + " " + arr[i[1]])
}
console.log(combinations(["apple", "banana", "lemon", "mango"]))
With more than ten elements, toString() will return letters for indices; also, this will only work with up to 36 Elements.
Generating combinations is a classic problem. Here's my interpretation of that solution:
const combinations = (elements) => {
if (elements.length == 1) {
return [elements];
} else {
const tail = combinations(elements.slice(1));
return tail.reduce(
(combos, combo) => { combos.push([elements[0], ...combo]); return combos; },
[[elements[0]], ...tail]
);
}
};
const array = ["apple", "banana", "lemon", "mango"];
console.log(combinations(array));
Here is an non-mutating ES6 approach combining things (TS):
function combine (tail: any[], length: number, head: any[][] = [[]]): any[][] {
return tail.reduce((acc, tailElement) => {
const tailHeadVariants = head.reduce((acc, headElement: any[]) => {
const combination = [...headElement, tailElement]
return [...acc, combination]
}, [])
if (length === 1) return [...acc, tailHeadVariants]
const subCombinations = combine(tail.filter(t => t !== tailElement), length - 1, tailHeadVariants)
return [...acc, ...subCombinations]
}, [])
}
As this post is well indexed on Google under the keywords "generate all combinations", lots of people coming here simply need to generate all the unique combinations, regardless of the size of the output (not only pairs).
This post answers this need.
All unique combinations, without recursion:
const getCombos = async (a) => {
const separator = '';
const o = Object();
for (let i = 0; i < a.length; ++i) {
for (let j = i + 1; j <= a.length; ++j) {
const left = a.slice(i, j);
const right = a.slice(j, a.length);
o[left.join(separator)] = 1;
for (let k = 0; k < right.length; ++k) {
o[[...left, right[k]].join(separator)] = 1;
}
}
}
return Object.keys(o);
}
const a = ['a', 'b', 'c', 'd'];
const b = await getCombos(a);
console.log(b);
// (14) ['a', 'ab', 'ac', 'ad', 'abc', 'abd', 'abcd',
// 'b', 'bc', 'bd', 'bcd', 'c', 'cd', 'd']
This code splits the array into 2 sub arrays, left / right, then iterate over the right array to combine it with the left array. The left becomes bigger overtime, while the right becomes smaller. The result has only unique values.
Beating a dead horse a bit, but with smaller sets where recursion limit and performance is not a problem, the general combination generation can be done recursively with "recurse combinations containing the first element in given array" plus "recurse combinations not containing the first element". It gives quite compact implementation as a generator:
// Generator yielding k-item combinations of array a
function* choose(a, k) {
if(a.length == k) yield a;
else if(k == 0) yield [];
else {
for(let rest of choose(a.slice(1), k-1)) yield [a[0], ...rest];
for(let rest of choose(a.slice(1), k)) yield rest;
}
}
And even slightly shorter (and twice faster, 1 M calls of 7 choose 5 took 3.9 seconds with my MacBook) with function returning and array of combinations:
// Return an array of combinations
function comb(a, k) {
if(a.length === k) return [a];
else if(k === 0) return [[]];
else return [...comb(a.slice(1), k-1).map(c => [a[0], ...c]),
...comb(a.slice(1), k)];
}

Categories