Trouble simplifying conditional - javascript

I'm having trouble simplifying a conditional statement. It looks like this:
There is an array of arrays like this, dice = [ [a, b], [c, d] ].
a, b, c, and d representing random numbers on a dice 1 > 6.
Every array in the array represents a roll of two dice where the last roll is [c, d] = [2, 5] for example, and the second-last roll is [a, b] = [3, 6] for example.
Now if you roll 2 x 1 in a row or 2 x 6 in a row something happens.
so, a + c || a + d and b + c || b + d may not be 2 or 12.
The conditional below works but I think if you open the door to hell it looks prettier.
dice = last roll = [c, d]
prev = second last roll = [a, b]
if (dice[0] + prev[0] === 2 || dice[1] + prev[1] === 2 || dice[1] + prev[0] === 2 || dice[0] +
prev[1] === 2 &&
dice[0] + prev[0] === 12 || dice[1] + prev[1] === 12 || dice[1] + prev[0] === 12 || dice[0] +
prev[1] === 12){
//something happens here
}

I would do something like this:
var combinations = dice.reduce(function(acc, curr) {
return acc.concat(prev.map(function(curr2) {
return curr + curr2;
}));
}, []);
if (combinations.indexOf(2) !== -1 && combinations.indexOf(12) !== -1) {
//something happens here
}
In ES6 it is a little bit shorter:
const combinations = dice.reduce((acc, curr) => acc.concat(prev.map(curr2 => curr + curr2)), []);
if (combinations.indexOf(2) !== -1 && combinations.indexOf(12) !== -1) {
//something happens here
}
What this actually does is calculate all combinations (additions) and then check whether one is 2 and one is 12.

The condition can only happens when both dice and prev contain 1 or 6. So, we can just check that.
let throws = [[5,3],[1,6]];
let prev = throws[0];
let dice = throws[1];
if ((prev.includes(1) && dice.includes(1)) || (prev.includes(6) && dice.includes(6))){
console.log("2 or 12 occurs")
}
else {
console.log("2 and 12 do not occur")
}

What about something like the following?:
const one_and_six_found = (roll) => roll.includes(1) && roll.includes(6);
const two_rolls_have_one_and_six = (roll1, roll2) =>
one_and_six_found(roll1) && one_and_six_found(roll2);
console.log(two_rolls_have_one_and_six( [1, 6], [1, 6] ));
console.log(two_rolls_have_one_and_six( [6, 1], [1, 6] ));
console.log(two_rolls_have_one_and_six( [5, 1], [1, 6] ));

I came out with a simpler solution more fitted to your need
const throws1 = [[1, 2], [3, 4]]
const throws2 = [[1, 2], [3, 1]]
const throws3 = [[1, 6], [6, 1]]
const checkCondition = (prev, dice) => [1, 6].every(res => prev.includes(res) && dice.includes(res))
console.log(checkCondition(...throws1))
console.log(checkCondition(...throws2))
console.log(checkCondition(...throws3))

Here's the conditional simplified:
2*(a + b + c + d) == a*b + c*d + 16

I wasn't 100% sure about a couple of these answers so I thought I'd test them:
const orig = ([a, b], [c, d]) =>
((a + c === 2 || b + d === 2 || b + c === 2 || a + d === 2) && (a + c === 12 || b + d === 12 || b + c === 12 || a + d === 12));
const A = ([a, b], [c, d]) => 2*(a + b + c + d) == a*b + c*d + 16;
const B = (prev, dice) => [1, 6].every(res => prev.includes(res) && dice.includes(res));
const one_and_six_found = (roll) => roll.includes(1) && roll.includes(6);
const C = (roll1, roll2) => one_and_six_found(roll1) && one_and_six_found(roll2);
const D = (prev, dice) => (prev.includes(1) && dice.includes(1)) || (prev.includes(6) && dice.includes(6));
const E = (prev, dice) => {
const combinations = dice.reduce((acc, curr) => acc.concat(prev.map(curr2 => curr + curr2)), []);
return (combinations.indexOf(2) !== -1 && combinations.indexOf(12) !== -1);
};
const test = (fn) => {
for(let i = 1; i <= 6; i++)
for(let j = 1; j <= 6; j++)
for(let k = 1; k <= 6; k++)
for(let l = 1; l <= 6; l++)
if(fn([i, j], [k, l]) !== orig([i, j], [k, l])) {
console.log('failed on ' + [i, j, k, l].join(', '));
return false;
}
return true;
};
console.log('A', test(A) ? 'success' : 'failed');
console.log('B', test(B) ? 'success' : 'failed');
console.log('C', test(C) ? 'success' : 'failed');
console.log('D', test(D) ? 'success' : 'failed');
console.log('E', test(E) ? 'success' : 'failed');

Related

Sum of to arrays with different array sizes

I'm trying to solve sum of to array problem:
//[1,2,3] + [1,2] should be [1,3,5]
I'm able to solve this if the array are the same size, but how can I deal with different array sizes?
Here is my code for now:
function sumOfArrays(a, b) {
let result = new Array(Math.max(a.length, b.length));
let carry = 0;
for (let i = result.length - 1; i >= 0; i--) {
const elementA = a[i];
const elementB = b[i];
const additionResult = elementA + elementB + carry;
result[i] = (additionResult % 10);
carry = Math.floor(additionResult / 10);
}
}
I'm basically getting null values into the result array If there is a difference in the size of the array
You could add a comparison if the 2 arrays are the same length.
If not, you can 'pad' it from the beginning with 0's until ther are the same length.
Then your code will work as expected (after adding return result ;) )
const pad = (arr, size, fill = 0) => [ ...Array(size - arr.length).fill(0), ...arr ];
let a = [1,2,3];
let b = [1,2];
if (a.length < b.length) {
a = pad(a, b.length);
} else if (b.length < a.length) {
b = pad(b, a.length);
}
function sumOfArrays(a, b) {
let result = new Array(Math.max(a.length, b.length));
let carry = 0;
for (let i = result.length - 1; i >= 0; i--) {
const elementA = a[i];
const elementB = b[i];
const additionResult = elementA + elementB + carry;
result[i] = (additionResult % 10);
carry = Math.floor(additionResult / 10);
}
return result;
}
const res = sumOfArrays(a, b);
console.log(res)
However, since the array's are now the same length, we can simplefy the code a lot by using map() and add (+) to current value of the other array on that index:
const pad = (arr, size, fill = 0) => [ ...Array(size - arr.length).fill(0), ...arr ];
let a = [1,2,3];
let b = [1,2];
if (a.length < b.length) {
a = pad(a, b.length);
} else if (b.length < a.length) {
b = pad(b, a.length);
}
function sumOfArrays(a, b) {
return a.map((n, i) => n + b[i]);
}
const res = sumOfArrays(a, b);
console.log(res)
// [
// 1,
// 3,
// 5
// ]
You could take an offset for the index to add.
function add(a, b) {
const
length = Math.max(a.length, b.length),
offsetA = a.length - length,
offsetB = b.length - length;
return Array.from(
{ length },
(_, i) => (a[i + offsetA] || 0) + (b[i + offsetB] || 0)
);
}
console.log(...add([1, 2, 3], [1, 2])); // [1, 3, 5]
console.log(...add([1, 2, 3], [4, 5, 6]));
console.log(...add([1, 2, 3, 4], [1])); // [1, 2, 3, 5]
console.log(...add([1], [1, 2, 3, 4])); // [1, 2, 3, 5]

create a function that accepts an integer and generates an array containing pairs of integers [a, b] sorted by increasing values of a and then b

I've gotten stuck on this practice problem in my software engineering bootcamp, and am hoping that someone can point me in the right direction.
Write a function generatePairs that accepts an integer and generates an array containing the pairs of integers [a, b]. The pairs should be sorted by increasing values of a then increasing values of b.
here are some examples of what should be returned for different inputs:
generatePairs(3) // [ [0, 0], [0, 1], [0, 2], [0, 3], [1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3] ]
generatePairs(2) // [ [0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2] ]
generatePairs(1) // [ [0, 0], [0, 1], [1,1]]
generatePairs(0) // [ [0, 0]]
and here is my code so far:
function generatePairs (num){
array = [];
// 0 [0,0] [0,1]
// 1
// 2
for (i = 0; i<=num; i++){
array.push([i,i]);
if ((i+1)<=num) {
array.push([i,i+1])
}
if ( num - i <= num && i===0 && num < i ) {
array.push([i,num])
if (num + i < i) {
array.pop();
}
}
}
return array;
}
generatePairs(2) // [ [0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2] ]
the issue I'm running into is that, for example, when I try it out with 2, I'm missing the [0,2] subarray. The methods I've tried to work around this have mostly consisted of additional if and else loops, but with each one I've tried I either end up with subarrays at the end that go higher than they should, or a semi-working system that would only work for 2 and not for any number that could be inputted into the function.
Isn't it much simpler than that?
function generatePairs(num) {
let arr = []
for (let i = 0; i <= num; i++) {
for (let j = i; j <= num; j++) {
arr.push([i, j])
}
}
return arr
}
console.log(generatePairs(2));
solution: (ninja code)
const generatePairs = n => Array.apply(null,{length:((n+1)*(n+2)/2)}).reduceRight((a,c,i)=>{
a.r.push([...a.p])
if(++a.p[1]>n) a.p[1]=++a.p[0]
return i?a:a.r
},{p:[0,0],r:[]})
for (let x=4;x--;) document.write(x,'->',JSON.stringify(generatePairs(x)), '<br>')
This is what you want right here. You loop through num for the first part until you get to num and for each first part loop from that first part to num.
function generatePairs(num) {
var output = []
for (var i = 0; i < num + 1; i++) {
for (var k = i; k < num + 1; k++) {
output.push([i, k])
}
}
return output
}
recursion
We can implement generatePairs in an wishful way -
const generatePairs = (n = 0) =>
chooseN(2, range(0, n))
range is a simple function that generates an array from a to b -
const range = (a = 0, b = 0) =>
a > b // base
? []
: [ a, ...range(a + 1, b) ] // inductive: a <= b
and chooseN is a generic that generates all n-sized samples from array a -
const chooseN = (n = 0, a = []) =>
n <= 0 // base
? [[]]
: a.length <= 0 // inductive: n > 0
? []
: chooseN(n - 1, a) // inductive: n > 0, non-empty a
.map(r => [a[0], ...r])
.concat(chooseN(n, a.slice(1)))
See generatePairs working in your own browser below -
const range = (a = 0, b = 0) =>
a > b
? []
: [ a, ...range(a + 1, b) ]
const chooseN = (n = 0, a = []) =>
n <= 0
? [[]]
: a.length <= 0
? []
: chooseN(n - 1, a)
.map(r => [a[0], ...r])
.concat(chooseN(n, a.slice(1)))
const generatePairs = (n = 0) =>
chooseN(2, range(0, n))
const log = x =>
console.log(JSON.stringify(x))
log(generatePairs(3))
// [[0,0],[0,1],[0,2],[0,3],[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]]
log(generatePairs(2))
// [[0,0],[0,1],[0,2],[1,1],[1,2],[2,2]]
log(generatePairs(1))
// [[0,0],[0,1],[1,1]]
log(generatePairs(0))
// [[0,0]]
generators
Because combinatorial problems typically involve big solution spaces, it's common to generate combinations lazily. In JavaScript, we can do this using generators -
const chooseN = function* (n = 0, a = [])
{ if (n <= 0)
return yield []
if (a.length <= 0)
return
for (const r of chooseN(n - 1, a))
yield [a[0], ...r]
yield* chooseN(n, a.slice(1))
}
Notice the structurally similarity between this program and the one above -
const chooseN = function* (n = 0, a = [])
{ if (n <= 0) // if (n <= 0)
return yield [] // return [[]]
if (a.length <= 0) // else if (a.length <= 0)
return // return []
// else return:
for (const r of chooseN(n - 1, a)) // chooseN(n - 1, a).map(r =>
yield [a[0], ...r] // [a[0],...r])
yield* chooseN(n, a.slice(1)) // .concat(chooseN(n, a.slice(1)))
}
For example, we could write a solver that finds the first pair, [ a, b ] where a > 3 and 3*a is equal to 2*b. Critically, no pairs will be generated after the first solution is found -
const solver = (size = 0) =>
{ for(const [a, b] of generatePairs(size))
if (a > 3)
if (3 * a === 2 * b)
return [a, b]
}
console.log(solver(10))
// [ 4, 6 ]
And the solution a = 4, b = 6 is correct: 4 > 3 is true and 3*4 is equal to 2*6 (12).
Below, we can generate the entire array of pairs, if we wish, using Array.from -
const allPairs =
Array.from(generatePairs(3)) // <-- Array.from exhausts an iterable
console.log(allPairs)
// [[0,0],[0,1],[0,2],[0,3],[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]]
Expand the snippet below to generate pairs using JavaScript's generators -
const range = (a = 0, b = 0) =>
a > b
? []
: [ a, ...range(a + 1, b) ]
const chooseN = function* (n = 0, a = [])
{ if (n <= 0)
return yield []
if (a.length <= 0)
return
for (const r of chooseN(n - 1, a))
yield [a[0], ...r]
yield* chooseN(n, a.slice(1))
}
const generatePairs = (n = 0) =>
Array.from(chooseN(2, range(0, n)))
const log = x =>
console.log(JSON.stringify(x))
log(generatePairs(3))
// [[0,0],[0,1],[0,2],[0,3],[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]]
log(generatePairs(2))
// [[0,0],[0,1],[0,2],[1,1],[1,2],[2,2]]
log(generatePairs(1))
// [[0,0],[0,1],[1,1]]
log(generatePairs(0))
// [[0,0]]

Strange behaviour of reduce method in JavaScript?

I was trying to create an even number array of Fibonacci series using Functional Programming - below code
let a = [1, 2];
const r = (n) =>
Array.from(
a[a.length - 1] + a[a.length - 2] <= n ?
a.push(a[a.length - 1] + a[a.length - 2]) && r(n) :
a
)
.filter(v => !(v % 2))
//.reduce((s, v) => s+=v, 0)
console.log(r(56))
It is giving correct array but when I wanted to calculate the sum (using reduce method by commenting the last line) it is giving 0 as a result
let a = [1, 2];
const r = (n) =>
Array.from(
a[a.length - 1] + a[a.length - 2] <= n ?
a.push(a[a.length - 1] + a[a.length - 2]) && r(n) :
a
)
.filter(v => !(v % 2))
.reduce((s, v) => s+=v, 0)
console.log(r(56))
in Repl.it (Link - https://repl.it/#rahul4sap/1). However, when I try to paste the same in Chrome Dev tools it is giving correct output. Can someone please help me explain why different behavior in Chrome Dev tool and Repl.it (same behaviour I see in local Node server)
Also, it will be good if someone please help me in fixing this as well (Please note I wanted to solve this in as much Functional way as possible)
Thanks in advance!
You could separate the functions an dget the fibonacci array first and then filter the array, and so on.
This approach uses a recursion by handing over a new build array.
const
add = (a, b) => a + b,
f = (n, a = [1, 2]) => a[a.length - 1] + a[a.length - 2] < n
? f(n, [...a, a[a.length - 1] + a[a.length - 2]])
: a,
r = n => f(n)
.filter(v => !(v % 2))
.reduce(add, 0);
console.log(r(56));
Consider a simple recursive function, fibs -
const fibs = (n = 0, a = 0, b = 1) =>
n <= 0
? []
: [ a, ...fibs(n - 1, b, a + b) ]
console.log(fibs(10)) // first 10 fib numbers
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]
Now add your .filter -
const fibs = (n = 0, a = 0, b = 1) =>
n <= 0
? []
: [ a, ...fibs(n - 1, b, a + b) ]
const evens =
fibs(10)
.filter(n => !(n & 1))
console.log(evens)
// [ 0, 2, 8, 34 ]
Now add your .reduce -
const fibs = (n = 0, a = 0, b = 1) =>
n <= 0
? []
: [ a, ...fibs(n - 1, b, a + b) ]
const sumEvens =
fibs(10)
.filter(n => !(n & 1))
.reduce((r, n) => r + n, 0)
console.log(sumEvens)
// 44
To see how you can compute fibonacci using other functional programming techniques, see this recent Q&A
Thank you for this. But I am looking for pushing element in an array (probably in a single function) until certain condition is met (like create Fibbonacci array until the last element is less than 100).
You change n = 0 to until = 0 and change the exit condition of your loop from n <= 0 to a > until -
const fibs = (until = 0, a = 0, b = 1) =>
a > until
? []
: [ a, ...fibs(until, b, a + b) ]
console.log(fibs(100))
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 ]
const sumEvens =
fibs(100)
.filter(n => !(n & 1))
.reduce((r, n) => r + n, 0)
console.log(sumEvens)
// 4
You may receive result in one line when you know how many fibonacci numbers do you need.
For example this code filter even numbers from the first 10 fibonacci numbers and calculate their sum:
let arr = (n) => [1, 2, ...Array(n-2)].reduce((acc, rec, idx) => (idx < 2) ? [...acc, rec] : [...acc, (acc[idx-2] + acc[idx-1])],[])
.filter(it => !(it % 2))
.reduce((s, v) => s+=v, 0)
console.log(arr(10))

Trying to Use Recursion to solve Fibonacci (javascript)

This is the question:
Given a positive integer num, return the sum of all odd Fibonacci numbers that are less than or equal to num.
The first two numbers in the Fibonacci sequence are 1 and 1. Every additional number in the sequence is the sum of the two previous numbers. The first six numbers of the Fibonacci sequence are 1, 1, 2, 3, 5 and 8.
For example, sumFibs(10) should return 10 because all odd Fibonacci numbers less than or equal to 10 are 1, 1, 3, and 5.
This is what I tried
function sumFibs(num, total = [1, 1], n = (total.length - 1 + total.length - 2)) {
if(n == num){
return total;
}
total.push(n);
sumFibs(num, n = (total.length - 1 + total.length - 2), total);
};
Question
Is it possible to use my method to make this work, if so how do I fix the syntax? If not, how would you solve the problem.
Many thanks!
continuation passing style
Continuation passing style effectively gives you programmatic return. Using a CPS function recursively can make program complexity evaporate into thin air -
const identity = x =>
x
const sumfib = (n = 0, then = identity) =>
n <= 0
? then(0, 1, 1) // base case
: sumfib // inductive: solve smaller subproblem
( n - 1
, (sum, fib, temp) =>
then(sum + fib, temp, fib + temp)
)
console.log
( sumfib(0) // 0 = 0
, sumfib(1) // 1 = 0 + 1
, sumfib(2) // 2 = 0 + 1 + 1
, sumfib(3) // 4 = 0 + 1 + 1 + 2
, sumfib(4) // 7 = 0 + 1 + 1 + 2 + 3
, sumfib(5) // 12 = 0 + 1 + 1 + 2 + 3 + 5
, sumfib(6) // 20 = 0 + 1 + 1 + 2 + 3 + 5 + 8
, sumfib(7) // 33 = 0 + 1 + 1 + 2 + 3 + 5 + 8 + 13
)
loop/recur
loop and recur give us the ability to write recursive programs like the one above, but will not encounter a stack overflow error -
const recur = (...values) =>
({ recur, values })
const loop = f =>
{ let r = f()
while (r && r.recur === recur)
r = f(...r.values)
return r
}
const sumfib = (n = 0) =>
loop // <-- loop with vars
( ( m = n
, sum = 0
, fib = 1
, temp = 1
) =>
m <= 0 // <-- exit condition
? sum // <-- base case
: recur // <-- recur with updated vars
( m - 1
, sum + fib
, temp
, temp + fib
)
)
console.log
( sumfib(0) // 0 = 0
, sumfib(1) // 1 = 0 + 1
, sumfib(2) // 2 = 0 + 1 + 1
, sumfib(3) // 4 = 0 + 1 + 1 + 2
, sumfib(4) // 7 = 0 + 1 + 1 + 2 + 3
, sumfib(5) // 12 = 0 + 1 + 1 + 2 + 3 + 5
, sumfib(6) // 20 = 0 + 1 + 1 + 2 + 3 + 5 + 8
, sumfib(7) // 33 = 0 + 1 + 1 + 2 + 3 + 5 + 8 + 13
)
streamz
so-called streams are interesting because they can possibly generate infinite values, but we don't have to compute them all at once. Again we can define our program in simple terms and let useful primitives do all of the hard work -
const fibs =
stream(0, _ =>
stream(1, _ =>
streamAdd(fibs, fibs.next)))
console.log(streamTake(fibs, 10))
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]
console.log(streamTake(streamSum(fibs), 10))
// [ 0, 1, 2, 4, 7, 12, 20, 33, 54, 88 ]
We just implement stream, streamAdd, streamSum, and streamTake -
const emptyStream =
Symbol('emptyStream')
const stream = (value, next) =>
( { value
, get next ()
{ delete this.next
return this.next = next()
}
}
)
const streamAdd = (s1, s2) =>
s1 === emptyStream || s2 === emptyStream
? emptyStream
: stream
( s1.value + s2.value
, _ => streamAdd(s1.next, s2.next)
)
const streamSum = (s, sum = 0) =>
s === emptyStream
? emptyStream
: stream
( sum + s.value
, _ => streamSum(s.next, sum + s.value)
)
const streamTake = (s = emptyStream, n = 0) =>
s === emptyStream || n <= 0
? []
: [ s.value, ...streamTake(s.next, n - 1) ]
Expand the snippet below to verify the results in your own browser -
const emptyStream =
Symbol('emptyStream')
const stream = (value, next) =>
( { value
, get next ()
{ delete this.next
return this.next = next()
}
}
)
const streamAdd = (s1, s2) =>
s1 === emptyStream || s2 === emptyStream
? emptyStream
: stream
( s1.value + s2.value
, _ => streamAdd(s1.next, s2.next)
)
const streamSum = (s, sum = 0) =>
s === emptyStream
? emptyStream
: stream
( sum + s.value
, _ => streamSum(s.next, sum + s.value)
)
const streamTake = (s = emptyStream, n = 0) =>
s === emptyStream || n <= 0
? []
: [ s.value, ...streamTake(s.next, n - 1) ]
const fibs =
stream(0, _ =>
stream(1, _ =>
streamAdd(fibs, fibs.next)))
console.log(streamTake(fibs, 10))
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]
console.log(streamTake(streamSum(fibs), 10))
// [ 0, 1, 2, 4, 7, 12, 20, 33, 54, 88 ]
Four things
(1) You don't return the result of the recursive call, therefore it does never get passed up to the caller:
sumFibs(4, [1, 1]) -> sumFibs(4, [1, 1, 2]) -> sumFibs(4, [1, 1, 2, 3])
<- [1, 1, 2, 3]
// v the return you do
// v the return you need too
(2) In the recursive call, the order of arguments is wrong.
(3) I guess instead of taking the arrays length minus 1, you want to access the property at that position in the total array.
(4) Why do you actually n as an argument? As it is only depending on total, it could also just be a variable:
function sumFibs(num, total = [1, 1]) {
const n = total[total.length - 1] + total[total.length - 2];
if(n > num){
return total;
}
total.push(n);
return sumFibs(num, total);
}
console.log(sumFibs(19));
This can be solved without an array accumulator; use n as a counter and curr and prev vars to store the data necessary to compute the Fibonacci series. Whenever we have an odd curr, add it to the running total and pass it up the call stack.
const sumOddFibs = (n, curr=1, prev=0) => {
if (curr < n) {
return sumOddFibs(n, curr + prev, curr) + (curr % 2 ? curr : 0);
}
return 0;
};
console.log(sumOddFibs(10));
As an aside, recursion is a pretty poor tool for just about anything that involves a sequential 0..n counter. Iteration makes more sense: less overhead, easier to understand and no risk of blowing the call stack. I'd also separate computation of the Fibonacci series (which is a good use case for a generator) from filtering oddness and summing so that each step is independent and can be reused:
const sum = arr => arr.reduce((a, e) => a + e);
const odds = arr => arr.filter(e => e % 2);
function *fibsBelow(n) {
for (let prev = 0, curr = 1; curr < n;) {
yield curr;
const tmp = curr;
curr += prev;
prev = tmp;
}
}
console.log(sum(odds([...fibsBelow(10)])));

Recursive function to get all unique combinations of two or more

I need to write a function that gets all unique combinations of two or more elements from an array of elements. I've been working on this for a few days now. I initially wrote separate functions to get a few different sized combinations, which helped me see how they're similar and I hoped would get me closer to a working solution than it has so far. This is what I have so far....
function getAll (elements, comboSize, startingIndex) {
let finalStartingIndex = /*startingIndex + */elements.length - comboSize;
let finalIndex = finalStartingIndex + 1;
let tmpStartingIndex = startingIndex;
let qstrings = [];
if (finalIndex >= elements.length) {
finalIndex = finalStartingIndex;
}
if (finalStartingIndex < finalIndex) {
while (tmpStartingIndex <= finalStartingIndex) {
let nextIndex = tmpStartingIndex + 1;
while (nextIndex <= finalIndex) {
let tmpComboSize = comboSize - 1;
let tmpQstring = '';
let newQstring = '';
if (tmpComboSize > 1) {
tmpQstring = getAll(elements, tmpComboSize, nextIndex);
console.log('tmpQstring :: ', tmpQstring);
}
if (tmpQstring != '') {
newQstring = elements[tmpStartingIndex] + ', ' + tmpQstring[nextIndex];
console.log('tmpQstring[nextIndex] :: ', tmpQstring[nextIndex]);
console.log('newQstring :: ', newQstring);
}
let qstring = elements[tmpStartingIndex] + ', ' + elements[nextIndex];
qstrings.push(qstring);
nextIndex++;
}
/*nextIndex = tmpStartingIndex;
let tmpComboSize = comboSize - 1;
if (tmpComboSize > 1) {
let tmpQstring = getAll(elements, tmpComboSize, nextIndex);
let stringVal = elements[tmpStartingIndex] + ', ' + tmpQstring[nextIndex];
qstrings.push(stringVal);
}*/
tmpStartingIndex++;
}
} else {
qstrings[finalStartingIndex] = elements[startingIndex] + ', ' + elements[finalStartingIndex];
console.log(qstrings);
}
return qstrings;
}
function getAllTwo (elements, comboSize, startingIndex) {
let finalStartingIndex = startingIndex + elements.length - comboSize;
let finalIndex = finalStartingIndex + 1;
let tmpStartingIndex = startingIndex;
let qstrings = [];
while (tmpStartingIndex <= finalStartingIndex) {
let finalNextIndex = tmpStartingIndex + 1;
while (finalNextIndex <= finalIndex) {
let qstring = elements[tmpStartingIndex] + ', ' + elements[finalNextIndex];
qstrings.push(qstring);
finalNextIndex++;
}
tmpStartingIndex++;
}
return qstrings;
}
function getAllThree (elements, comboSize, startingIndex) {
let finalStartingIndex = startingIndex + elements.length - comboSize;
let finalFirstNextIndex = finalStartingIndex + 1;
let finalIndex = finalFirstNextIndex + 1;
let tmpStartingIndex = startingIndex;
let qstrings = [];
while (tmpStartingIndex <= finalStartingIndex) {
let firstNextIndex = tmpStartingIndex + 1;
while (firstNextIndex <= finalFirstNextIndex) {
let finalNextIndex = firstNextIndex + 1;
while (finalNextIndex <= finalIndex) {
let qstring = elements[tmpStartingIndex] + ', ' + elements[firstNextIndex] + ', ' + elements[finalNextIndex];
qstrings.push(qstring);
finalNextIndex++;
}
firstNextIndex++;
}
tmpStartingIndex++;
}
return qstrings;
}
function getAllFour (elements, comboSize, startingIndex) {
let finalStartingIndex = startingIndex + elements.length - comboSize;
let finalFirstNextIndex = finalStartingIndex + 1;
let finalSecondNextIndex = finalFirstNextIndex + 1;
let finalIndex = finalSecondNextIndex + 1;
let tmpStartingIndex = startingIndex;
let qstrings = [];
while (tmpStartingIndex <= finalStartingIndex) {
let firstNextIndex = tmpStartingIndex + 1;
while (firstNextIndex <= finalFirstNextIndex) {
let secondNextIndex = firstNextIndex + 1;
while (secondNextIndex <= finalSecondNextIndex) {
let finalNextIndex = secondNextIndex + 1;
while (finalNextIndex <= finalIndex) {
let qstring = elements[tmpStartingIndex] + ', ' + elements[firstNextIndex] + ', ' + elements[secondNextIndex] + ', ' + elements[finalNextIndex];
qstrings.push(qstring);
finalNextIndex++;
}
secondNextIndex++;
}
firstNextIndex++;
}
tmpStartingIndex++;
}
return qstrings;
}
function getAllFive (elements, comboSize, startingIndex) {
let finalStartingIndex = startingIndex + elements.length - comboSize;
let firstFinalIndex = finalStartingIndex + 1;
let secondFinalIndex = firstFinalIndex + 1;
let thirdFinalIndex = secondFinalIndex + 1;
let finalIndex = thirdFinalIndex + 1;
let tmpStartingIndex = startingIndex;
let qstrings = [];
while (tmpStartingIndex <= finalStartingIndex) {
let firstIndex = tmpStartingIndex + 1;
while (firstIndex <= firstFinalIndex) {
let secondIndex = firstIndex + 1;
while (secondIndex <= secondFinalIndex) {
let thirdIndex = secondIndex + 1;
while (thirdIndex <= thirdFinalIndex) {
let finalNextIndex = thirdIndex + 1;
while (finalNextIndex <= finalIndex) {
let qstring = elements[tmpStartingIndex] + ', ' + elements[firstIndex] + ', ' + elements[secondIndex] + ', ' + elements[thirdIndex] + ', ' + elements[finalNextIndex];
qstrings.push(qstring);
console.log('qstrings being built: ', qstrings);
finalNextIndex++;
}
thirdIndex++;
}
secondIndex++;
}
firstIndex++;
}
tmpStartingIndex++;
}
return qstrings;
}
let finalStrings = [];
let elements = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for (let comboSize = 2; comboSize <= elements.length; comboSize++) {
let finalString = [];
let tstFinalString = [];
switch (comboSize) {
case 2:
tstFinalString = getAll(elements, comboSize, 0);
//console.log('tstFinalString, comboSize 2 :: ', tstFinalString);
//finalString = getAllTwo(elements, comboSize, 0);
break;
case 3:
tstFinalString = getAll(elements, comboSize, 0);
console.log('tstFinalString, comboSize 3 :: ', tstFinalString);
//finalString = getAllThree(elements, comboSize, 0);
break;
/*case 4:
finalString = getAllFour(elements, comboSize, 0);
break;
case 5:
finalString = getAllFive(elements, comboSize, 0);
console.log(finalString);
break;*/
default:
break;
}
finalStrings.push(finalString);
}
The first function is my attempt at proper recursion. Currently, it can get all two element combinations, but it cannot go beyond that. I feel like there's something simple that I'm missing, and I just cannot see it. I wrote the other functions to help me think through the logic and to make sure I was getting the data I am expecting. Those functions do work, but obviously it's not scalable. Any help you can offer pointing out what I'm missing will be appreciated.
Oh, this is currently written in Javascript, if that matters.
I think this would be more or less the canonical way for k sized combinations.
// return n choose k combinations
function choose(arr, k, prefix=[], i=0){
// if the remainder of the array will complete the
// combination length exactly, combine it with
// the current prefix and add to results
if (prefix.length + arr.length - i == k){
return [prefix.concat(arr.slice(i))];
// if the prefix is long enough, add it to the results
} else if (prefix.length == k){
return [prefix];
// otherwise, push combinations with and without
// the current element
} else {
return choose(arr, k, prefix.concat(arr[i]), i + 1)
.concat(choose(arr, k, prefix, i + 1));
}
}
let arr = ["A","B","C","D","E"];
console.log('Input: ' + JSON.stringify(arr) + '\n');
let cs = choose(arr, 3);
console.log('\nOutput:');
for (let c of cs)
console.log(JSON.stringify(c));
To get all, we could do something like:
function powerset(arr, prefix=[], i=0){
if (i == arr.length)
return [prefix];
return powerset(arr, prefix.concat(arr[i]), i + 1)
.concat(powerset(arr, prefix, i + 1));
}
let arr = ['a', 'b', 'c', 'd', 'e'];
let ps = powerset(arr);
for (let s of ps)
console.log(JSON.stringify(s));
Here's another way using generators. The advantage of this approach is that the combinations are generated lazily. Depending on your computation, sometimes you may be able to arrive at your desired result before generating all combinations. Since many combinatory problems involve huge sets of combinations, we can potentially save a lot of resources from skipping the generation of unneeded combinations. Additionally, this concept could be expanded to work on infinite streams, where it's possible that an infinite number of combinations could result.
const append = (xs, x) =>
xs .concat ([ x ])
const ncomb = function* (n, xs = [])
{ const gen = function* (n, acc)
{ if (n === 0)
yield acc
else
for (const x of xs)
yield* gen (n - 1, append (acc, x))
}
yield* gen (n, [])
}
const data =
[ 1, 2, 3 ]
const print = (...xs) =>
console .log (...xs .map (x => JSON .stringify (x)))
print
( Array .from (ncomb (0, data))
// [[]]
, Array .from (ncomb (1, data))
// [[1],[2],[3]]
, Array .from (ncomb (2, data))
// [[1,1],[1,2],[1,3],[2,1],[2,2],[2,3],[3,1],[3,2],[3,3]]
, Array .from (ncomb (3, data))
// [[1,1,1],[1,1,2],[1,1,3],[1,2,1],[1,2,2],[1,2,3],[1,3,1],[1,3,2],[1,3,3],[2,1,1],[2,1,2],[2,1,3],[2,2,1],[2,2,2],[2,2,3],[2,3,1],[2,3,2],[2,3,3],[3,1,1],[3,1,2],[3,1,3],[3,2,1],[3,2,2],[3,2,3],[3,3,1],[3,3,2],[3,3,3]]
)
Above the combinations increment the rightmost element, but this ordering can be changed. If you change the append operation to prepend you will generate combinations where the leftmost element increments instead –
const prepend = (xs, x) =>
[ x ] .concat (xs)
print
( Array .from (ncomb (3, data))
// [[1,1,1],[2,1,1],[3,1,1],[1,2,1],[2,2,1],[3,2,1],[1,3,1],[2,3,1],[3,3,1],[1,1,2],[2,1,2],[3,1,2],[1,2,2],[2,2,2],[3,2,2],[1,3,2],[2,3,2],[3,3,2],[1,1,3],[2,1,3],[3,1,3],[1,2,3],[2,2,3],[3,2,3],[1,3,3],[2,3,3],[3,3,3]]
)
To demonstrate the short-circuit behavior of the generator, consider a function solve which takes any amount of numbers and returns the first Pythagorean triple. After the first right triangle is found, no additional combinations will be generated -
const isRightTriangle = (a, b, c) =>
// pythagorean triple
(a ** 2) + (b ** 2) === (c ** 2)
const solve = (...numbers) =>
{ for (const [ a, b, c ] of ncomb (3, numbers))
if (isRightTriangle (a, b, c))
return `solution: ${a}, ${b}, ${c}`
return `no solution`
}
console .log (solve (1, 2, 3, 4, 5, 6, 7, 9, 10))
// solution: 3, 4, 5
console .log (solve (8, 11, 6, 9, 5, 10, 12, 7))
// solution: 8, 6, 10
console .log (solve (19, 6, 8, 7, 21, 4, 3, 15))
// no solution
Expand the snippet below to verify the results in your own browser -
const append = (xs, x) =>
xs .concat ([ x ])
const ncomb = function* (n, xs = [])
{ const gen = function* (n, acc)
{ if (n === 0)
yield acc
else
for (const x of xs)
yield* gen (n - 1, append (acc, x))
}
yield* gen (n, [])
}
const isTriangle = (a, b, c) =>
// pythagorean theorem
(a ** 2) + (b ** 2) === (c ** 2)
const solve = (...numbers) =>
{ for (const [ a, b, c ] of ncomb (3, numbers))
if (isTriangle (a, b, c))
return `solution: ${a}, ${b}, ${c}`
return `no solution`
}
console .log (solve (1, 2, 3, 4, 5, 6, 7, 9, 10))
// solution: 3, 4, 5
console .log (solve (8, 11, 6, 9, 5, 10, 12, 7))
// solution: 8, 6, 10
console .log (solve (19, 6, 8, 7, 21, 4, 3, 15))
// no solution

Categories