How to convert nested loop in to recursion? - javascript

I want to convert this nested loops in recursion. How do I achieve this?
for(let i = 0; i < 5; i++) {
for(let j = 0; j < 5; j++) {
console.log(i,j);
}
}

Here another example of this recursion:
function loop(i,j,limitI,limitJ){
if(i>=limitI) return;
if(j>=limitJ) loop(i+1,0,limitI,limitJ);
else{
console.log(i,j);
loop(i,j+1,limitI,limitJ)
}
}
loop(0,0,4,4);

Generic function product calculates the Cartesian product of its inputs - You can polyfill Array.prototype.flatMap if it's not already in your environment
Array.prototype.flatMap = function (f, context)
{
return this.reduce ((acc, x) => acc.concat (f (x)), [])
}
const product = (first = [], ...rest) =>
{
const loop = (comb, first, ...rest) =>
rest.length === 0
? first.map (x => [ ...comb, x ])
: first.flatMap (x => loop ([ ...comb, x ], ...rest))
return loop ([], first, ...rest)
}
const suits =
['♤', '♡', '♧', '♢']
const ranks =
['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
for (const card of product (ranks, suits))
console.log (card)
// [ 'A', '♤' ]
// [ 'A', '♡' ]
// [ 'A', '♧' ]
// [ 'A', '♢' ]
// [ '2', '♤' ]
// ...
// [ 'Q', '♧' ]
// [ 'K', '♤' ]
// [ 'K', '♡' ]
// [ 'K', '♧' ]
// [ 'K', '♢' ]
product is a variadic function (by use of a rest parameter) which accepts 1 or more inputs
const range = (min = 0, max = 0) =>
max < min
? []
: [ min, ...range (min + 1, max) ]
const r =
range (0, 2)
for (const comb of product (r, r, r))
console.log (comb)
// [ 0, 0, 0 ]
// [ 0, 0, 1 ]
// [ 0, 0, 2 ]
// [ 0, 1, 0 ]
// ...
// [ 2, 1, 2 ]
// [ 2, 2, 0 ]
// [ 2, 2, 1 ]
// [ 2, 2, 2 ]
Using destructuring assignment, you can effectively create nested loops
for (const [ i, j ] of product (range (0, 5), range (0, 5)))
console.log ("i %d, j %d", i, j)
// i 0, j 0
// i 0, j 1
// i 0, j 2
// i 0, j 3
// i 0, j 4
// i 0, j 5
// i 1, j 0
// ...
// i 4, j 5
// i 5, j 0
// i 5, j 1
// i 5, j 2
// i 5, j 3
// i 5, j 4
// i 5, j 5
product can also be written using generators - below, we find all perfect Pythagorean triples under 20
const product = function* (first, ...rest)
{
const loop = function* (comb, first, ...rest)
{
if (rest.length === 0)
for (const x of first)
yield [ ...comb, x ]
else
for (const x of first)
yield* loop ([ ...comb, x ], ...rest)
}
yield* loop ([], first, ...rest)
}
const range = (min = 0, max = 0) =>
max < min
? []
: [ min, ...range (min + 1, max) ]
const pythagTriple = (x, y, z) =>
(x * x) + (y * y) === (z * z)
const solver = function* (max = 20)
{
const N = range (1, max)
for (const [ x, y, z ] of product (N, N, N))
if (pythagTriple (x, y, z))
yield [ x, y, z ]
}
console.log ('solutions:', Array.from (solver (20)))
// solutions:
// [ [ 3, 4, 5 ]
// , [ 4, 3, 5 ]
// , [ 5, 12, 13 ]
// , [ 6, 8, 10 ]
// , [ 8, 6, 10 ]
// , [ 8, 15, 17 ]
// , [ 9, 12, 15 ]
// , [ 12, 5, 13 ]
// , [ 12, 9, 15 ]
// , [ 12, 16, 20 ]
// , [ 15, 8, 17 ]
// , [ 16, 12, 20 ]
// ]
I think using map (and reduce), while it allows for more complex recursive structures as you demonstrate, is actually an implicit for loop, which does not really answer the question on how to convert one into a recurrence. However, if you also defined a recursive map and reduce, then it would be OK :) - גלעד ברקן
Your wish is my command :D
const Empty =
Symbol ()
const concat = (xs, ys) =>
xs.concat (ys)
const append = (xs, x) =>
concat (xs, [ x ])
const reduce = (f, acc = null, [ x = Empty, ...xs ]) =>
x === Empty
? acc
: reduce (f, f (acc, x), xs)
const mapReduce = (m, r) =>
(acc, x) => r (acc, m (x))
const map = (f, xs = []) =>
reduce (mapReduce (f, append), [], xs)
const flatMap = (f, xs = []) =>
reduce (mapReduce (f, concat), [], xs)
const product = (first = [], ...rest) =>
{
const loop = (comb, first, ...rest) =>
rest.length === 0
? map (x => append (comb, x), first)
: flatMap (x => loop (append (comb, x), ...rest), first)
return loop ([], first, ...rest)
}
const suits =
['♤', '♡', '♧', '♢']
const ranks =
['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
for (const card of product (ranks, suits))
console.log (card)
// same output as above

I don't recommend this but you can do following(as it is difficult to read, for readability and understandability your code is best).
function forLoop(i,j){
if(j===0){
if(i!==0)
forLoop(i-1,4);
console.log(i,j);
}
else{
forLoop(i,j-1);
console.log(i,j);
}
}
forLoop(4,4);

Here's my rendition:
function nested(i, j, maxI, maxJ) {
if (i == maxI) return
console.log(i, j)
if (i < maxI) {
++j < maxJ ? nested(i, j, maxI, maxJ) : nested(++i, 0, maxI, maxJ)
}
}
nested(0, 0, 5, 5)

This is an alternative.
This approach uses param initialization with comma operator (just to make the code shorter).
Additionally, an operator param (callback) to execute any logic for each iteration.
function loop(n, operator, i = 0, j = 0) { // Param initialization.
if (j === n) (j = 0, i++); // Comma operator.
if (i === n) return;
operator(i, j);
loop(n, operator, i, ++j);
}
loop(5, (i, j) => console.log(i, j));
.as-console-wrapper { max-height: 100% !important; top: 0; }

You could use an array for limit and values. The order is reversed, because of the incrementing of the lowest index first.
This works for an arbitrary count of nested loops and it allowes to use an arbitrary limit of the max values.
function iter(limit, values = limit.map(_ => 0)) {
console.log(values.join(' '));
values = values.reduce((r, v, i) => {
r[i] = (r[i] || 0) + v;
if (r[i] >= limit[i]) {
r[i] = 0;
r[i + 1] = (r[i + 1] || 0) + 1;
}
return r;
}, [1]);
if (values.length > limit.length) {
return;
}
iter(limit, values);
}
iter([2, 3]);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Here's an outline of a "recurrence relation," where "each further term of the sequence ... is defined as a function of the preceding terms."
As you are probably aware, recursive functions usually have at least one base case, terminating the recursion, and at least one recursive call. To find a pattern, let's examine the sequence:
0,0
0,1
0,2
0,3
0,4
1,0
1,2
...
Our base case, where the call to a preceding parameter terminates, seems to be 0,0. But this is also where the console logs begin, which means we first have to call all the way back to the base case. For convenience, let's assume the function expects positive parameters:
function f(i, j){
if (i == 0 && j == 0){
console.log(i,j);
return;
}
}
We can also notice that the outer loop, the i, stays constant for each cycle of js:
function f(i, j){
if (i == 0 && j == 0){
console.log(i,j);
return;
}
if (j == 0)
// ... what happens here?
}
but here we get stuck. When j is greater than zero, we can determine that the current term came from f(i, j - 1), but if j is zero in the current term, we have no way of formulating what it was in the preceding term. We need one more parameter:
function f(i, j, jj){
if (i == 0 && j == 0){
console.log(i,j);
return;
}
if (j == 0)
f(i - 1, jj, jj);
else
f(i, j - 1, jj);
console.log(i,j);
}
f(4,4,4);

You could recurse by taking a depth and the values to iterate:
function loop(start, end, depth, exit, ...args){
for(let i = start; i < end; i++)
depth ? loop(start, end, depth - 1, exit, ...args, i) : exit(...args, i);
}
Usable as:
loop(0, 5, 1, (i, j) => console.log(i, j))
The only real usecase would be deeper loops, e.g. this one
If you want it completely without for:
const range = (start, end, cb) =>
(cb(start), start + 1 >= end || range (start + 1, end, cb));
function loop(start, end, depth, exit, ...args){
range(start, end, i =>
depth ? loop(start, end, depth - 1, exit, ...args, i) : exit(...args, i));
}
Try it

Transforming a nested for loop into its recursive counterpart is surprisingly hard. Good question!
You can transform every loop (without a stack) into a tail recursive algorithm. So this rule should hold for a nested loop too.
I think we need two distinct functions to get something equivalent to your two nested loops:
const loop = ([i, j], [k, l]) => {
const loop_ = (k_, l_) => {
if (k_ >= l_) return;
else {
console.log(i, k_);
loop_(k_ + 1, l_);
}
};
if (i >= j) return;
else {
loop_(k, l);
loop([i + 1, j], [k, l]);
}
};
loop([0, 5], [0, 5]);
You have to pass ranges for both the out and the inner loop.
As you can see both recursive calls are in tail position. I think this is the closest equivalent we can get.

suggested solution
function recurse(arg1=0, arg2=0, cb) {
if ( arg2 <= 5 ) {
let _l = arg2++;
if ( arg1 === 5 )
return ;
if ( ++_l === 6 ) {
arg2 = 0;
cb(arg1++, arg2);
recurse(arg1, arg2, cb);
} else {
cb(arg1, arg2 - 1);
recurse(arg1, arg2, cb);
}
}
}
recurse( 0 , 0 , (i,j) => console.log(i,j));

Related

Javascript: Replace same consecutive elements of an array into one item that shows their count in

I have an array like this :
const array = [1, 3, x, x, 4, x, x, x, 9, x, x, x, x, 7]
I want to turn all consecutive elements which has the value of x into one element that shows their count between numbers such as :
const newArray = [1, 3, '2x', 4, '3x', 9, '4x', 7]
For instance, 3 consecutive x in the array like [x, x, x] should be turned into one ['3x'] while numbers should stay untouched.
Maybe there's a better solution for achieving that. Of course, there's! But here is a solution by using the for loop. Check every iteration if the element is x, increase the x value by 1. And if the current element is not x, then reset the x variable to 0.
Sample Result:
const array = [1, 3, 'x', 'x', 4, 'x', 'x', 'x', 9, 'x', 'x', 'x', 'x', 7];
let x = 0;
let result = [];
for (let i = 0; i < array.length; i++) {
if (array[i] === 'x') {
x++;
if (array[i + 1] === 'x') continue;
result.push(`${x}x`);
continue;
}
x = 0;
result.push(array[i]);
}
console.log(result);
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Loops_and_iteration
const array = [1, 3, 'x', 'x', 4, 'x', 'x', 'x', 9, 'x', 'x', 'x', 'x', 7]
const newArray = array.map(item => `${item}`).reduce((acc, curr)=>{
if(curr !== 'x') return [...acc, curr];
const previous = acc.pop();
if(!previous || !previous.includes('x')) return [...acc, previous, '1x'];
const xCounter = 1 + parseInt(previous.match(/\d+/)[0]);
return [...acc, `${xCounter}x`];
},[])
console.log(newArray)
The simplest way is just to iterate all elements:
// Your array
const array = [
1,
3,
"x",
"x",
4,
"x",
"x",
"x",
9,
"x",
"x",
"x",
"x",
7
];
// Create new array
const newArr = [];
// Set internal value repeat counter
let cn = 1;
// Iterate array
for(let i = 0; i < array.length; i++) {
// Set trigger
let trig = 1;
// If next param exist and it is equals current
if(array[i+1] && array[i+1] === array[i]) {
// Increase counter
cn++;
// Set trigger to false
trig = 0;
}
// If trigger is true
if(trig) {
// If internal counter greater then 1
// push previous value and its counter
if(cn > 1) {
newArr.push(`${cn}${array[i-1]}`);
// Reset counter
cn = 1;
}
// Else just push current value
else newArr.push(array[i]);
}
}
// Test
console.log(newArr);
And then you can try to shrink the code with something more advanced:
// Your array
const arr = [1,3,"x","x",4,"x","x","x",9,"x","x","x","x",7];
// Use flatMap
let cn = 1;
const res = arr.flatMap((e, i, a) => {
return a[i+1] && a[i+1] == e ? (cn++) && [] :
cn > 1 ? (()=>{const t = cn; cn = 1; return t+''+a[i-1]})() : e;
});
// Result
console.log(res);

Sort Array by Element Frequency JavaScript

I want to sort an array by element frequency. My code works for arrays of strings, but not for arrays of numbers:
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach(i => d[i] = countOccurrences(arr,i))
arr.sort(function(a,b){
return d[b] - d[a]
})
return arr
}
frequencySort(['a','b','b','b','c','c'])) returns [ 'b', 'b', 'b', 'c', 'c', 'a' ]
frequencySort([4, 6, 2, 2, 6, 4, 4, 4]) returns [ 4, 4, 4, 4, 6, 2, 2, 6 ]
The only reason your letters worked is because you didn't have the same number of any two letters, where in your numbers, you have 2 of both 2 and 6.
Here's your snippet, but with 2 a's and 2 c's. You'll see it's out of order just like the numbers.
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach(i => d[i] = countOccurrences(arr,i))
arr.sort(function(a,b){
return d[b] - d[a]
})
return arr
}
console.log(frequencySort(['a','b','b','b','c','c', 'a']))
You need a way to sort instances that have the same number of occurrences. I adapted your forEach loop to give the last index of each letter to your b object and then changed your sort to use that index in case the number of occurrences is the same.
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach((i,index) => d[i] = {
num: countOccurrences(arr,i),
i: index
});
arr.sort(function(a,b){
let diff = d[b].num - d[a].num;
if(diff == 0)
diff = d[b].i - d[a].i;
return diff;
})
return arr
}
console.log(frequencySort(['a','b','b','b','c','c', 'a']))
console.log(frequencySort([4, 6, 2, 2, 6, 4, 4, 4]));
It has nothing to do with the elements being letters or numbers. In you letters array, each letter has unique occurence count (3, 2, 1), therefore they are sorted the way you want to.
However, in your numbers array, "2" and "6" both occur 2 times each. Therefore, your sort callback function returns 0 for them, and they are treated as equal order by the sort function.
In your array of numbers you have the same amount of the number 2 as 6 and your sorting function doesn't care about the actual values it just cares about their counts. So in your example 2 and 6 both have the same priority.
You want to adjust your sorting function to compare values of elements if they have the same amount of occurrences.
You'll need to implement separate comparisons for all the data types you want to accept and decide if you want ascending/descending order.
Here is a basic example for number and string elements:
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach(i => d[i] = countOccurrences(arr,i))
arr.sort(function(a,b){
const r = d[b] - d[a]
if (r != 0) return r
switch (typeof d[a]) {
case 'number': return a - b
case 'string': return a.localeCompare(b)
default: return 0
}
})
return arr
}
console.log(frequencySort(['a','b','b','b','c','c'])) // returns [ 'b', 'b', 'b', 'c', 'c', 'a' ]
console.log(frequencySort([4, 6, 2, 2, 6, 4, 4, 4])) // returns [ 4, 4, 4, 4, 2, 2, 6, 6 ]
A possible approach would first collect all equal array items within an item specific group array by a reduce task ...
console.log(
"grouped ['a','b','b','b','c','c'] ...",
['a','b','b','b','c','c'].reduce((index, item) => {
const groupList =
index[`${ (typeof item) }_${ item }`] ??= [];
groupList.push(item);
return index;
}, {})
);
console.log(
"grouped [4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'] ...",
[4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'].reduce((index, item) => {
const groupList =
index[`${ (typeof item) }_${ item }`] ??= [];
groupList.push(item);
return index;
}, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
The final computation then has to transform ... via Object.values ... the temporary result (as shown above) into an array of arrays of equal items where the former gets 1stly sorted by each array's length (indicates the items frequency) and 2ndly, for arrays of equal length', by a locale compare of each array's first item. The final result is the sorted array's flatted version ...
function sortItemsByFrequency(arr) {
const groupedItems = arr.reduce((index, item) => {
const groupList =
index[`${ (typeof item) }_${ item }`] ??= [];
groupList.push(item);
return index;
}, {});
return Object
.values(groupedItems)
.sort((a, b) =>
// - sort by frequency first indicated by an
// array's length.
// - the higher frequency count wins.
b.length - a.length ||
// in case of equal frequency counts do a
// locale compare of both array's first items.
b[0].toLocaleString().localeCompare(a[0].toLocaleString())
)
.flat();
}
console.log(
"sortItemsByFrequency(['a','b','b','b','c','c']) ...",
sortItemsByFrequency(['a','b','b','b','c','c'])
);
console.log(
"sortItemsByFrequency([4, 6, 2, 2, 6, 4, 4, 4,'4','2','2']) ...",
sortItemsByFrequency([4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'])
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
sort first based on frequency of characters in desc order,if freq is same, then sort alphabetically in asc order.
const str = 'zzzzvnttteeeqqaao';
const frequencySort = (str = '') => {
let map = {}
for (const letter of str) {
map[letter] = (map[letter] || 0) + 1;
};
let res = "";
let sorted = Object.keys(map).sort((a, b) => map[b] < map[a] ? -1 : 1);
//console.log(sorted);
for (let letter of sorted) {
for (let count = 0; count < map[letter]; count++) {
res += letter
}
}
return res;
};
console.log(frequencySort(str));

Create array of all N digit numbers whose sum of digit equals S

I found some solutions to my question in other languages. When I converted them to javascript, it would not create an array.
const find_digits = (n, sum, out, index) => {
if (index > n || sum < 0) return;
let f = "";
if (index == n) {
if (sum == 0) console.log(out); // Success!
return;
}
for (var i = 0; i < 10; i++) {
out[index] = i;
find_digits(n, sum - i, out, index + 1);
}
}
const subset_sum = (n, sum) => {
var out = [].fill(false, 0, n + 1);
for (var i = 0; i < 10; i++) {
out[0] = i;
find_digits(n, sum - i, out, 1);
}
return out;
}
console.log(subset_sum(3, 17)); // Output: [9,9,9]
The first log is successful, but the final log returns [9,9,9]
I would appreciate some help.
I might make the recursion a bit more explicit. To my mind there are a number of different base-cases, for various low values of n and s:
if s < 0, then there are no results
if s == 0, then the only result is a string of n zeroes
if n == 1, then
if s < 10, then the only result is the digit s
otherwise, there are no results
The recursive case involves taking each digit as a potential first digit, then joining it with each of the results involved in recursing, taking that amount from the total and using a digit count one smaller.
Here's an implementation of that:
const subsetSum = (n, s) =>
s < 0
? []
: s == 0
? ['0' .repeat (n)]
: n == 1
? s < 10 ? [String(s)] : []
: // else
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] .flatMap (
k => subsetSum (n - 1, s - k) .map (p => k + p)
)
console .log (
subsetSum (3, 17)
) //~> ["089", "098", "179", "188", "197", "269", "278", ..., "971", "980"] (63 entries)
.as-console-wrapper {min-height: 100% !important; top: 0}
Given your comment about licenses, I assume that it's really strings of digits you want and not numbers. That's what this returns. If you want numbers, you will need to remove all those that start with 0 and convert the strings to numbers. (Thus, 89 would not be included in subset(17, 3) even thought "089" is a legitimate digit string, because 89 is only a two-digit number.)
Update
I just realized that the s == 0 case can be subsumed in the recursive one. So it's actually a bit simpler:
const subsetSum = (n, s) =>
s < 0
? []
: n == 1
? s < 10 ? [String(s)] : []
: // else
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] .flatMap (
k => subsetSum (n - 1, s - k) .map (p => k + p)
)
Or, phrased slightly differently, as
const subsetSum = (n, s) =>
s < 0 || (n <= 1 && s >= 10)
? []
: n == 1
? [String(s)]
: // else
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] .flatMap (
k => subsetSum (n - 1, s - k) .map (p => k + p)
)
const find_digits = (n, sum, out, index) => {
if (index >= n) return;
out[index] = 9;
find_digits(n, sum, out, index +1);
}
const subset_sum = (n, sum) => {
var out = [].fill(false, 0, n + 1);
find_digits(n, sum, out, 0);
return out;
}
console.log(subset_sum(3, 17));
I guess the problem is with the check at the beginning of the find_digit() function for the number of times you can call this function won't exceed by any means 3 times as per your arguments passed in the subset_sum() function. I have redacted the code to the bare minimum and produced the same result as yours. If you can give me clarification as to the purpose of this code. I would be glad to help.
You should call your function without console, like this:
subset_sum(3, 17)
Because you write to console inside your code.
Your trouble is that your subset_sum returning at the end array out.
So, you have 2 options:
- call your function by name
- returning at the end of subset_sum just "return"
The function below allows you to pass in a different base/alphabet.
It first generates the permutations, then filters the results based on the value you passed in as the second parameter.
const BaseSystem = {
bin : '01',
dec : '0123456789',
hex : '0123456789ABCDEF'
};
Object.keys(BaseSystem).forEach(k =>
Object.assign(BaseSystem, { [k] : BaseSystem[k].split('') }))
const main = () => {
console.log(subsetSum(4, v => v === 2, BaseSystem.bin))
console.log(subsetSum(2, 4)) // Default = BaseSystem.dec
console.log(subsetSum(3, 0xF, BaseSystem.hex))
}
const subsetSum = (size, sum, alpha=BaseSystem.dec) => {
if (typeof sum === 'string') sum = parseInt(sum, alpha.length)
return getPermutations(alpha, size)
.filter(v => ((result) =>
typeof sum === 'function' ? sum(result, v) : sum === result
)(parseReduce(alpha.length, ...v))).map(v => v.join(''))
}
const parseReduce = (base, ...v) =>
v.reduce((t, x) => t + parseInt(x, base), 0)
/** Adapted From: https://stackoverflow.com/a/59028925/1762224 */
const getPermutations = (list, len) => {
const base = list.length
const counter = Array(len).fill(base === 1 ? arr[0] : 0)
if (base === 1) return [counter]
const results = []
const increment = i => {
if (counter[i] === base - 1) {
counter[i] = 0
increment(i - 1)
} else counter[i]++
}
for (let i = base ** len; i--;) {
const subResults = []
for (let j = 0; j < counter.length; j++)
subResults.push(list[counter[j]])
results.push(subResults)
increment(counter.length - 1)
}
return results
}
main();
.as-console-wrapper {min-height: 100% !important; top: 0}

Find all permutations of 2 arrays in JS

I'm trying to find every permutations of 2 arrays like this:
// input
lowerWords = ['one', 'two', 'three' ]
upperWords = [ 'ONE', 'TWO', 'THREE' ]
// output
keywords = {
'one two three': true,
'ONE two three': true,
'ONE TWO three': true,
'ONE TWO THREE': true,
'ONE two THREE': true,
'one TWO three': true,
'one two THREE': true,
'one TWO THREE': true,
}
It should function with more than 3 items, both arrays will always be same length. This is my code:
const keywords = {}
const lowerWords = ['one', 'two', 'three' ]
const upperWords = [ 'ONE', 'TWO', 'THREE' ]
const wordCount = lowerWords.length
let currentWord = 0
let currentWords = [...upperWords]
while (currentWord < wordCount) {
currentWords[currentWord] = lowerWords[currentWord]
let keyword = currentWords.join(' ')
keywords[keyword] = true
currentWord++
}
currentWord = 0
currentWords = [...lowerWords]
while (currentWord < wordCount) {
currentWords[currentWord] = upperWords[currentWord]
let keyword = currentWords.join(' ')
keywords[keyword] = true
currentWord++
}
result is missing some
ONE TWO THREE: true
ONE TWO three: true
ONE two three: true
one TWO THREE: true
one two THREE: true
one two three: true
You could transpose the arrays for getting an array of pairs and then get all combinations of the pairs.
const
transpose = array => array.reduce((r, a) => a.map((v, i) => [...(r[i] || []), v]), []),
combinations = array => array.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []));
var lowerWords = ['one', 'two', 'three'],
upperWords = ['ONE', 'TWO', 'THREE'],
pairs = transpose([lowerWords, upperWords]),
result = combinations(pairs);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Thought I'd give it a try. I used binary to get the possible combinations, as this problem needed a base 2 solution:
const low = ["one", "two", "three"];
const up = ["ONE", "TWO", "THREE"];
const words = [low, up]
const len = words[0].length
function getCombinations(noOfArrays, len) {
var temp, newCombo, combos = [];
for (var i = 0; i < (noOfArrays ** len); i++) {
temp = new Array(len).fill(0)
newCombo = i.toString(noOfArrays).split('');
newCombo.forEach((el, i) => temp[temp.length - newCombo.length + i] = +el);
combos.push(temp);
}
return combos;
}
function setCombinations(combos) {
return combos.map(combo => combo.map((el, i) => words[el][i]))
}
var combos = getCombinations(words.length, len)
combos = setCombinations(combos)
console.log(combos)
Explanation of loop:
1. temp = new Array(len).fill(0)
2. newCombo = i.toString(2).split("");
3. newCombo.forEach((el, i) => temp[temp.length - newCombo.length + i] = +el);
Creates temp array [0,0,0]
Grabs loop number (i) and converts it to binary e.g:
1 -> 1
2 -> 10
3 -> 11
4 -> 100
etc...
Then split the binary into an array 100 -> [1,0,0].
Then for each element push it in that new array. This gave a problem with pushing the 1 and 2 element arrays (10 -> [1,0]) into the back of the array. I used temp.length - newCombo.length + i to fix that.
That function then returns:
[ 0, 0, 0 ]
[ 0, 0, 1 ]
[ 0, 1, 0 ]
[ 0, 1, 1 ]
[ 1, 0, 0 ]
[ 1, 0, 1 ]
[ 1, 1, 0 ]
[ 1, 1, 1 ]
Then, I can map over each combination, and grab each array depending on the value, and get the words ('one' or 'ONE') via loop index.
Note this code works with more than one array, as long as the arrays are all the same length.
You need to get a total of 2 ^ 3 combinations. If you create a 2D matrix from the 2 arrays, the below table represents the row number from which of the item should be taken.
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1
If you analyze indexes of the combinations, each of this a binary number from 0 to 2 ^ 3 with leading zeros.
So, you could
loop from 0 to 8
create binary number using toString(2)
Add leading zero using padStart
split each digit to get an array
Get each item from matrix[digit-from-binary][position-of-each-split]
join the array of items with a ' ' separator to get the key
Add the key to the output object
Working snippet:
function getAllCombinations(matrix) {
const combinations = 2 ** 3,
output = {};
for(let i = 0; i < combinations; i++) {
const key = i.toString(2)
.padStart(3, 0)
.split('')
.map((n, j) => matrix[n][j])
.join(" ")
output[key] = true;
}
return output
}
console.log(getAllCombinations([['one', 'two', 'three' ],[ 'ONE', 'TWO', 'THREE' ]]))
You can generalize this for m x n matrix. Instead of converting each to a binary number, you need to convert it to base-m and padStart to length n
function getAllCombinations(matrix) {
const rows = matrix.length,
columns = matrix[0].length,
combinations = rows ** columns;
return Array.from({ length: combinations },
(_, i) => i.toString(rows)
.padStart(columns, 0)
.split('')
.map((n, j) => matrix[n][j])
)
}
console.log(JSON.stringify(
getAllCombinations( [[1, 2, 3], [4, 5, 6], [7, 8, 9]] ) // 3 x 3 matrix
));
console.log(JSON.stringify(
getAllCombinations( [[1, 2], [3, 4], [5, 6], [7, 8]] ) // 4 x 2 matrix
));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Following Code should work for you its recursive approach:
const lowerWords = ['one', 'two', 'three']
const upperWords = ['ONE', 'TWO', 'THREE']
let result = {};
function getCombinations(index, caseType, arr) {
if (index == 3) {
arr[index] = (caseType == 'lower' ? lowerWords : upperWords)[index];
result[arr.join(' ')] = true
return
}
arr[index] = (caseType == 'lower' ? lowerWords : upperWords)[index];
getCombinations(index + 1, 'lower', arr);
getCombinations(index + 1, 'upper', arr);
}
getCombinations(0, 'lower', [])
getCombinations(0, 'upper', [])
console.log('resultresult', result)
Here's a generalised version based on my other answer that handles a variable number of input arrays with varying lengths:
const g = (arrs, i=0, comb=[]) =>
!arrs.some(arr => i < arr.length)
? [comb]
: arrs.reduce((acc, arr) =>
i >= arr.length ? acc :
acc.concat(g(arrs, i + 1, comb.slice().concat(arr[i])))
, [])
// Example output
let input = [['ONE','TWO','THREE'], ['one','two'], [1,2,3,4]]
let str = ''
for (let line of g(input))
str += JSON.stringify(line) + '\n'
console.log(str)
We can enumerate these directly. Here's one algorithm:
If we've reached the end of
the list, return the combination
the current recursion branch is
building.
Otherwise, create a new branch
that picks the next item from B,
while the current branch picks
the next item from A.
JavaScript code:
function f(A, B, i=0, comb=[]){
return i == A.length
? [comb]
: f(A, B, i + 1, comb.concat(A[i])).concat(
f(A, B, i + 1, comb.slice().concat(B[i])))
}
console.log(JSON.stringify(f(['one','two','three'], ['ONE','TWO','THREE'])))

How to find all partitions of a multiset, where each part has distinct elements?

Let's say we have such an array:
myArray = [A, A, B, B, C, C, D, E]
I would like to create an algorithm so that it will find all the combinations that add up to the whole array, where none of the elements are repeated.
Example combinations:
[A, B, C, D, E] [A, B, C]
[A, B, C, D] [A, B, C, E]
[A, B, C] [A, B, C] [D, E]
Clarification: [A, B, C] [A, B, C] [D, E] and [A, B, C] [D, E] [A, B, C] are the same combinations. Also the ordering with the subsets doesn't matter as well. For example [A,B,C] and [B,A,C] should be the same.
So far, I didn't progress beyond
var myArray = ["A", "A", "B", "B", "C", "C", "D", "E"]
console.log([...new Set(myArray)])
But this doesn't help at all, it just returns one distinct set.
I couldn't find a similar problem posted before, so could anyone guide me here in how to achieve this?
I'm getting 315 combinations. Is that right? :)
Here's a recursion:
function distribute(e, i, _comb){
// No more e's
if (e[1] == 0)
return [_comb];
// We're beyond the combination
if (i == -1)
return [_comb.concat([e])];
let result = [];
for (let c=1; c<=Math.min(_comb[i][1], e[1]); c++){
let comb = _comb.map(x => x.slice());
if (c == comb[i][1]){
comb[i][0] += e[0];
} else {
comb[i][1] -= c;
comb.push([comb[i][0] + e[0], c]);
}
result = result.concat(distribute([e[0], e[1] - c], i - 1, comb));
}
let comb = _comb.map(x => x.slice());
return result.concat(distribute(e, i - 1, comb));
}
function f(arr){
function g(i){
if (i == 0)
return [[arr[0]]];
const combs = g(i - 1);
let result = [];
for (let comb of combs)
result = result.concat(
distribute(arr[i], comb.length - 1, comb));
return result;
}
return g(arr.length - 1);
}
function show(arr){
const rs = f(arr);
const set = new Set();
for (let r of rs){
const _r = JSON.stringify(r);
if (set.has(_r))
console.log('Duplicate: ' + _r);
set.add(_r);
}
let str = '';
for (let r of set)
str += '\n' + r
str += '\n\n';
console.log(JSON.stringify(arr));
console.log(set.size + ' combinations:');
console.log(str);
}
show([['A', 2], ['B', 2], ['C', 2], ['D', 1], ['E', 1]]);
You could do it by enumerating all possible combinations, and then finding the permutatinos for every combination, and then filter the element to make sure they are unique. This filtering can be done by inserting into a Set.
The subset function is from #le_m (Check this answer).
function* subsets(array, offset = 0) {
while (offset < array.length) {
let first = array[offset++];
for (let subset of subsets(array, offset)) {
subset.push(first);
yield subset;
}
}
yield [];
}
function* permutations(elements) {
if (elements.length === 1) {
yield elements;
} else {
let [first, ...rest] = elements;
for (let perm of permutations(rest)) {
for (let i = 0; i < elements.length; i++) {
let start = perm.slice(0, i);
let rest = perm.slice(i);
yield [...start, first, ...rest];
}
}
}
}
const arr = ['A', 'a', 'B', 'b', 'C', 'c', 'd', 'e'];
const results = new Set();
for (let subset of subsets(arr)) {
if (subset.length) {
for (let permut of permutations(subset)) {
results.add(permut.join(''));
}
}
}
console.log([...results]);
This generates all possible permutations of the wanted result, so it does not answer the question.
function* combinations(combos) {
yield combos;
for(const [i1, group] of combos.entries()) {
for(const [i2, group2] of combos.slice(i1 + 1).entries()) {
if(group.some(v => group2.includes(v))) continue;
yield* combinations([
...combos.filter((_, index) => index !== i1 && index !== i2),
[...group, ...group2]
]);
}
}
}
console.log([...combinations([1, 2, 3, 4, 1].map(v => ([v])))]);
You could start with an array with combinations that contain only one element, then go over those combinations and merge two arrays of them, proceed recursively.
Playground
You could first group same elements and count them, resulting in a table like this:
1 | 2 | 3 | 4
1 | | 3 | 4
1
(1 is duplicated twice, 3 and 4 once)
Now you could start and take the first four elements out, then the 3 in the second row and then the one in the last row resulting in
[[1, 2, 3, 4], [1, 3, 4], [1]]
Now to get the next combo, just take 3 out from the first row, and let the other values move up:
1 | | 3 | 4
1 | | | 4
now again take out the rows, and you get
[[1, 2, 3], [1, 3, 4], [1, 4]]
Now repeat this pattern with the first row (take 2, 1) and so on, also repeat that with the rows, then you should get the result you want to achieve.
const counts = new Map;
for(const value of [1, 1, 1, 2, 3, 3, 4, 4])
counts.set(value, (counts.get(value) || 0) + 1);
const result = [];
for(let combo = 0; combo < counts.size; combo++) {
const subResult = [];
const combos = [...counts];
for(let i = 0; i <= combo; i++) combos[i][0] -= 1;
subResult.push(combos.slice(0, combo).map(([_, value]) => value);
while(combos.some(([count]) => count > 0)) {
subResult.push(combos.filter(([count]) => count > 0).map(([_, value] => value));
}
result.push(subResult);
}
Implemented an algorithm from scratch that:
gets the frequency of the letters,
starts at the key length and works downwards towards 1
while the frequency map is not empty
add the key to the sub-result
const decrementKey = (o, k) => o[k]--;
const isEmpty = (o) => Object.keys(o).length === 0;
const removeKeys = (o) => Object.keys(o).forEach(k => { if (o[k] < 1) delete o[k]; });
const frequencyMap = (a) => a.reduce((r, v) => Object.assign(r, { [v] : (r[v] || 0) + 1 }), {});
const cloneMap = (o) => Object.keys(o).reduce((r, k) => Object.assign(r, { [k] : o[k] }), {});
let myArray = ["A", "A", "B", "B", "C", "C", "D", "E"];
let groups = solve(myArray);
console.log(groups.map(g => g.map(a => a.join('')).join(' | ')).join('\n'));
function solve(arr) {
let freq = frequencyMap(arr), results = [];
for (let max = Object.keys(freq).length; max > 1; max--) {
let result = [], clone = cloneMap(freq);
while (!isEmpty(clone)) {
result.push(Object.keys(clone).reduce((res, key, index) => {
if (index < max) {
decrementKey(clone, key);
res.push(key);
}
return res;
}, []));
removeKeys(clone);
}
if (result.length > 0) results.push(result);
}
return results;
}
.as-console-wrapper { top: 0; max-height: 100% !important; }

Categories

Resources