how to improve this algorithm for combinations? - javascript

I have this problem, I need to find a combination with specific numbers, and the sum of the numbers should be a other specific amount, i think that you can understand me with the code.
function get4() {
function iter(temp) {
return function (v) {
var t = temp.concat(v);
if (t.length === 4) {
if (t.reduce(add) === 10) {
result.push(t);
}
return;
}
values.forEach(iter(t));
};
}
const
add = (a, b) => a + b,
values = [1, 2, 3, 4],
result = [];
values.forEach(iter([]));
return result;
}
console.log(get4().map(a => a.join(' ')));
with this code I can find a 4 digits that his sum is 10, with little numbers works, but if a try with bigger numbers, the function crash, I mean, the browser not execute it.
my problema is with this data
length = 493
the sum is = 42990
and the values are = [500,400,300,200,100,90,80,70,60,50,40,30,20,10,5]
how can I improve this code? if you have another solution in other language, it would also help me.

You could:
// comments removed for simplicity
function combinationSumRecursive(
candidates,
remainingSum,
finalCombinations = [],
currentCombination = [],
startFrom = 0,
) {
if (remainingSum < 0) {
return finalCombinations;
}
if (remainingSum === 0) {
finalCombinations.push(currentCombination.slice());
return finalCombinations;
}
for (let candidateIndex = startFrom; candidateIndex < candidates.length; candidateIndex += 1) {
const currentCandidate = candidates[candidateIndex];
currentCombination.push(currentCandidate);
combinationSumRecursive(
candidates,
remainingSum - currentCandidate,
finalCombinations,
currentCombination,
candidateIndex,
);
currentCombination.pop();
}
return finalCombinations;
}
function removesDuplicatesAndNon4Length(arr) {
return arr.map(x => [...new Set(x)]).filter(x => x.length === 4);
}
const tempResp = combinationSumRecursive([1, 2, 3, 4], 10);
const resp = removesDuplicatesAndNon4Length(tempResp);
console.log(resp);
Note: This is a Combination Sum Problem version modificated for this specific program.

Related

How could I find intersections in a string array with a variable number of elements?

I have a function that receives an array composed of numerical, comma separated strings as input, then finds the intersectional numbers in those strings and returns a string of similarly comma separated numbers, with no spaces, containing those intersections. If there are no intersections between the two elements, the function will return false.
What I want is to optimize the function so that it can work with a string array that may have more than just two elements. Is that possible? If so, could I have some sort of guideline of where to start looking for answers?
Currently, this is what I have.
function LocateIntersection(strArr) {
let arrHalf1 = strArr[0].split(", ");
let arrHalf2 = strArr[1].split(", ");
let interArr = arrHalf1.filter(value => arrHalf2.includes(value));
let result = interArr.join();
if (result) {
return result;
} else {
return false;
}
}
My answer is a little flawed, but it should meet your requirements
function LocateIntersection(strArr) {
const AllArrHalf = strArr.map((value) => value.split(', ')).sort((a, b) => b.length - a.length);
const lastArrHalf = AllArrHalf[AllArrHalf.length - 1];
let interArr = [];
AllArrHalf.forEach((value, index) => {
if (index !== AllArrHalf.length - 1) {
interArr.push(lastArrHalf.filter(value1 => value.includes(value1)))
}
})
if (interArr.length > 1) {
let result = interArr.map(value => value.join(', '));
LocateIntersection(result);
} else if (interArr.length === 1) {
result = interArr.join();
console.log(result);
}
}
LocateIntersection(['a, b, c', 'a, b', 'a, b'])
You can try this.
const intersection = (arr1, arr2) => {
return arr2.filter(element => arr1.includes(element));
}
const getIntersection = (stringArray, prevResult) => {
const array1 = prevResult || stringArray[0].split(', ');
const array2 = stringArray.shift().split(', ');
const result = intersection(array1, array2);
console.log(`result : `, result)
if(result.length > 0 && stringArray.length > 0) {
return getIntersection(stringArray, result);
}
return result;
}
const input = ['1, 2','1, 3, 3, 3','123, 222','1, 1, 1','1','3, 2, 3, 1'];
const result = getIntersection(input);
console.log('final Result:',result);

Javascript - Counting array elements by reduce method until specific value occurs doesn't give a correct output

const arr = [5,6,0,7,8];
const sum = (arr,num) => arr.reduce((total)=>(num==0 ? total : total+num), 0)
console.log(sum(arr, 0))
Please check how can I make it work. Did some mistake but don't know what exactly. Output is a function instead of a result.
This is awkward to do in .reduce because it goes through the entire array. If we do a naive implementation you can see the problem:
const arr = [5,6,0,7,8];
const sum = (arr,num) => arr.reduce((total, x)=>(num==x ? total : total+x), 0)
console.log(sum(arr, 0))
We now make the check correctly - num==x will return true when x is zero (the value of num). However, the result is wrong because this only returns true once but any other iteration it's still true. And here is the same thing with more logging that describes each step of the process:
const arr = [5,6,0,7,8];
const sum = (arr,num) => arr.reduce((total, x)=> {
const boolCheck = num==x;
const result = boolCheck ? total : total+x;
console.log(
`total: ${total}
num: ${num}
x: ${x}
boolCheck: ${boolCheck}
result: ${result}`);
return result;
}, 0)
console.log(sum(arr, 0))
So, you need to add some flag that persists between iterations, so it doesn't get lost.
One option is to have an external flag that you change within the reduce callback:
const arr = [5,6,0,7,8];
const sum = (arr,num) => {
let finished = false;
return arr.reduce((total, x) => {
if(x === num)
finished = true;
return finished ? total : total+x;
}, 0)
}
console.log(sum(arr, 0))
Alternatively, you can have that flag internal to the reduce callback and pass it around between calls. It works the same way in the end but makes the callback function pure. At the cost of some unorthodox construct:
const arr = [5,6,0,7,8];
const sum = (arr,num) => {
return arr.reduce(({total, finished}, x) => {
if(x === num)
finished = true;
total = finished ? total : total+x;
return {total, finished};
}, {total: 0, finished: false})
.total
}
console.log(sum(arr, 0))
If you want to use reduce but you're OK with using other methods, then you can use Array#indexOf to find the first instance of a value and Array#slice the array that contains any value up to the target value:
const arr = [5,6,0,7,8];
const sum = (arr,num) => {
const endIndex = arr.indexOf(num);
return arr.slice(0, endIndex)
.reduce((total, x)=> total+x, 0)
}
console.log(sum(arr, 0))
Or in as one chained expression:
const arr = [5,6,0,7,8];
const sum = (arr,num) => arr
.slice(0, arr.indexOf(num))
.reduce((total, x)=> total+x, 0);
console.log(sum(arr, 0))
Other libraries may have a takeUntil or takeWhile operation which is even closer to what you want - it gets you an array from the beginning up to a given value or condition. You can then reduce the result of that.
Here is an example of this using Lodash#takeWhile
By using chaining here, Lodash will do lazy evaluation, so it will only go through the array once, instead of scanning once to find the end index and going through the array again to sum it.
const arr = [5,6,0,7,8];
const sum = (arr,num) => _(arr)
.takeWhile(x => x !== num)
.reduce((total, x)=>total+x, 0)
console.log(sum(arr, 0))
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.15/lodash.min.js"></script>
As a note, if you are using Lodash, then you may as well use _.sum(). I didn't above just to illustrate how a generic takeUntil/takeWhile looks.
const arr = [5, 6, 0, 7, 8];
const sum = (arr, num) => _(arr)
.takeWhile(x => x !== num)
.sum()
console.log(sum(arr, 0))
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.15/lodash.min.js"></script>
Since you need to stop summing values part way through the array, this might be most simply implemented using a for loop:
const arr = [5, 6, 0, 7, 8];
const num = 0;
let sum = 0;
for (let i = 0; i < arr.length; i++) {
if (arr[i] == num) break;
sum += arr[i];
}
console.log(sum);
If you want to use reduce, you need to keep a flag that says whether you have seen the num value so you can stop adding values from the array:
const arr = [5, 6, 0, 7, 8];
const sum = (arr, num) => {
let seen = false;
return arr.reduce((c, v) => {
if (seen || v == num) {
seen = true;
return c;
}
return c + v;
}, 0);
}
console.log(sum(arr, 0));
console.log(sum(arr, 8));
call it as follows:
console.log(sum(arr, 0)());
You need parenthesis to execute the function ()
sum(arr, 0)
Without parenthesis you store a reference to the function in the variable

What's wrong in my code? It fails just 2 of the test cases without showing any error

I have started learning programming on my own just a few months back. So pardon me if my question sounds a bit silly.
One of the challenges on freeCodeCamp needs to define a function that takes an array with 2 values as an input and the function should return the LCM of all the numbers within that range inclusive of those 2 numbers.
My code below passes the tests number 1,2,3,6 given in the exercise. but somehow fails for the tests 4 & 5. Also freeCodeCamp is not showing any error! So I am unable to figure out what am I doing wrong in the below code.
function smallestCommons(arr) {
let allNum = [];
for (let i = Math.min(...arr); i <= Math.max(...arr); i++) {
allNum.push(i);
}
function findFactors(x) {
let allFactors = [];
for (let i = 1; i <= x; i++) {
if (x % i == 0) {
allFactors.push(i);
}
}
return allFactors;
}
function findGCF(a,b) {
return findFactors(a).filter(item => findFactors(b).includes(item)).reduce((p,q) => p*q);
}
return allNum.reduce((a,b) => ((a*b)/findGCF(a,b)));
}
The tests given in the exercise are as follows. My code passes 1,2,3 & 6 but fails 4 & 5.
smallestCommons([1, 5]) should return a number.
smallestCommons([1, 5]) should return 60.
smallestCommons([5, 1]) should return 60.
smallestCommons([2, 10]) should return 2520.
smallestCommons([1, 13]) should return 360360.
smallestCommons([23, 18]) should return 6056820.
function smallestCommons(arr) {
let allNum = [];
for (let i = Math.min(...arr); i <= Math.max(...arr); i++) {
allNum.push(i);
}
function findFactors(x) {
let allFactors = [];
for (let i = 1; i <= x; i++) {
if (x % i == 0) {
allFactors.push(i);
}
}
return allFactors;
}
function findGCF(a,b) {
return findFactors(a).filter(item => findFactors(b).includes(item)).reduce((p,q) => p*q);
}
return allNum.reduce((a,b) => ((a*b)/findGCF(a,b)));
}
console.log(smallestCommons([1, 5])); // should return a number.
console.log(smallestCommons([1, 5])); // should return 60.
console.log(smallestCommons([5, 1])); // should return 60.
console.log(smallestCommons([2, 10])); // should return 2520.
console.log(smallestCommons([1, 13])); // should return 360360.
console.log(smallestCommons([23, 18])); // should return 6056820.
Your findGCF function is off. To find the GCF of two numbers, you should find the largest factor which evenly divides both. Eg
findGCF(60, 6)
should be 6. (But yours returns 36)
function findFactors(x) {
// console.log(x);
let allFactors = [];
for (let i = 1; i <= x; i++) {
if (x % i === 0) {
allFactors.push(i);
}
}
return allFactors;
}
function findGCF(a, b) {
const bFac = findFactors(b);
return findFactors(a)
.filter(item => bFac.includes(item))
.reduce((p, q) => p * q);
}
console.log(findGCF(60, 6)); // should be 6
To reduce the computational complexity and fix it at the same time, make a Set of one of the factor collections, then iterate over an array of the other factor collections, starting from the largest factor and working your way downwards, and .find the first factor which is contained in the Set (it may end up being 1):
function findGCF(a, b) {
const bFacSet = new Set(findFactors(b));
return findFactors(a)
.reverse()
.find(item => bFacSet.has(item));
}
Fix that, and your smallestCommons function works as desired:
function smallestCommons(arr) {
const allNum = [];
for (let i = Math.min(...arr); i <= Math.max(...arr); i++) {
allNum.push(i);
}
function findFactors(x) {
const allFactors = [];
for (let i = x; i >= 1; i--) {
if (x % i === 0) {
allFactors.push(i);
}
}
return allFactors;
}
function findGCF(a, b) {
const bFacSet = new Set(findFactors(b));
return findFactors(a)
.find(item => bFacSet.has(item));
}
return allNum.reduce((a,b) => ((a*b)/findGCF(a,b)));
}
console.log(smallestCommons([2, 10])) // should return 2520.
console.log(smallestCommons([1, 13])) // should return 360360.

How to generate an array of every permutation of a sequence, with duplicates?

I've had a look around this site but I have been unable to find an answer that includes duplicate elements. For example, given the array:
[1,2,3,4]
With a length of 3, A function should generate a list of every single possible combination with those numbers, using each one more than once:
[
[1,1,1],
[1,1,2],
[1,1,3],
...
[4,4,2],
[4,4,3],
[4,4,4]
]
I just haven't been able to get my head around the algorithm that I should use. I don't expect a code answer, but a push in the right direction would be appreciated.
I've tried using reduce like so:
const arr = [1, 2, 3, 4]
const len = 3
arr.reduce((acc, n) => {
for (let i = 0; i < len; i++) {
acc.push(/* ???? */)
}
return acc
}, [])
but I really don't know how to continue.
As a side note, ideally, I would like to do this as efficiently as possible.
One approach would be to use the length of the array as a base. You could then just access the array's elements from 0, and count up to the amount of combinations (array length ** length). If you're working with a small dataset, performance really shouldn't be an issue, but this answer is very performance oriented:
const getCombos = (arr, len) => {
const base = arr.length
const counter = Array(len).fill(base === 1 ? arr[0] : 0)
if (base === 1) return [counter]
const combos = []
const increment = i => {
if (counter[i] === base - 1) {
counter[i] = 0
increment(i - 1)
} else {
counter[i]++
}
}
for (let i = base ** len; i--;) {
const combo = []
for (let j = 0; j < counter.length; j++) {
combo.push(arr[counter[j]])
}
combos.push(combo)
increment(counter.length - 1)
}
return combos
}
const combos = getCombos([1, 2, 3, 4], 3)
console.log(combos)
You could take an algorithm for getting a cartesian prduct with an array of three arrays with the wanted values.
var values = [1, 2, 3, 4],
length = 3,
result = Array
.from({ length }, _ => values)
.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []));
console.log(result.map(a => a.join(' ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Picture an ancient mechanical rotary counter:
To count from 00000 to 99999 you rotate the rightmost wheel until it reaches 9, then it resets to 0 and the second right wheel is advanced by 1 etc.
In code: locate the rightmost wheel which position is less than the max. digit. Advance that wheel by 1 and reset all wheels on the right of it to 0. Repeat until there's no such wheel.
function counter(digits, size) {
let wheels = Array(size).fill(0),
len = digits.length,
res = [];
while (1) {
res.push(wheels.map(n => digits[n]));
for (let i = size - 1; i >= 0; i--) {
if (wheels[i] < len - 1) {
wheels[i]++;
wheels.fill(0, i + 1);
break;
}
if (i === 0)
return res;
}
}
}
all = counter('1234', 3)
console.log(...all.map(c => c.join('')))
Performance measures:
const kobe = (arr, len) => {
const base = arr.length
const counter = Array(len).fill(base === 1 ? arr[0] : 0)
if (base === 1) return [counter]
const combos = []
const increment = i => {
if (counter[i] === base - 1) {
counter[i] = 0
increment(i - 1)
} else {
counter[i]++
}
}
for (let i = base ** len; i--;) {
const combo = []
for (let j = 0; j < counter.length; j++) {
combo.push(arr[counter[j]])
}
combos.push(combo)
increment(counter.length - 1)
}
return combos
}
function nina(values, length) {
return Array
.from({length}, _ => values)
.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []));
}
function trincot(digits, size) {
if (!size) return [[]]; // base case
let shorter = trincot(digits, size - 1); // get all solutions for smaller size
// ...and prefix each of those with each possible digit
return Array.from(digits, dig => shorter.map(arr => [dig, ...arr])).flat();
}
function georg(digits, size) {
let wheels = Array(size).fill(0),
len = digits.length,
res = [];
while (1) {
res.push(wheels.map(n => digits[n]));
for (let i = size - 1; i >= 0; i--) {
if (wheels[i] < len - 1) {
wheels[i]++;
wheels.fill(0, i + 1);
break;
}
if (i === 0)
return res;
}
}
}
const gilad = (A, k) =>
k ? gilad(A, k - 1).reduce((a, s) => a.concat(A.map(e => s.concat(e))), []) : [[]]
//////////////////////////////////////////////////////////////
fns = [kobe, nina, trincot, georg, gilad];
ary = [0, 1, 2, 3, 4, 5, 6, 7, 8]
size = 5
res = []
for (f of fns) {
console.time(f.name);
res.push(f(ary, size));
console.timeEnd(f.name)
}
Using the same technique to generate cartesian products:
function product(...arrays) {
let size = arrays.length,
wheels = Array(size).fill(0),
lens = arrays.map(a => a.length),
res = [];
while (1) {
res.push(wheels.map((n, w) => arrays[w][n]));
for (let w = size - 1; w >= 0; w--) {
if (wheels[w] < lens[w] - 1) {
wheels[w]++;
wheels.fill(0, w + 1);
break;
}
if (w === 0)
return res;
}
}
}
// demo
p = product('12', 'abcde', 'XYZ')
console.log(...p.map(q => q.join('')))
// some timings
// https://stackoverflow.com/a/43053803/989121
const f = (a, b) => [].concat(...a.map(d => b.map(e => [].concat(d, e))));
const cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a);
arrays = Array(7).fill(0).map(_ => Array(5).fill(0)) // 5**7=78125 combinations
console.time('func')
cartesian(...arrays)
console.timeEnd('func')
console.time('iter')
product(...arrays)
console.timeEnd('iter')
Maybe also add the recursive solution:
function counter(digits, size) {
if (!size) return [[]]; // base case
let shorter = counter(digits, size-1); // get all solutions for smaller size
// ...and prefix each of those with each possible digit
return Array.from(digits, dig => shorter.map(arr => [dig, ...arr])).flat();
}
// demo
let all = counter("1234", 3);
console.log(...all.map(c => c.join("")));
This is known as "n multichoose k" and has the following recurrence relation:
function f(ns, n, k){
if (n == 0)
return []
if (k == 0)
return [[]]
return f(ns, n - 1, k).concat(
f(ns, n, k - 1).map(s => s.concat(ns[n-1])))
}
var multiset = [1, 2, 3, 4]
var k = 3
console.log(JSON.stringify(f(multiset, multiset.length, k)))
An alternative, as others have answered, is to also include every permutation of every combination. One way is to append each element to each combination as we build towards the final length. (This idea is similar to trincot's.)
const f = (A, k) =>
k ? f(A, k - 1).reduce((a, s) => a.concat(A.map(e => s.concat(e))), []) : [[]]
var A = [1, 2, 3, 4]
var k = 3
console.log(JSON.stringify(f(A, k)))

Serializing Array of Many Duplicates

So I have a series of arrays, each of which are 2500 long, and I need to serialize and store all them in very limited space.
Since I have many duplicates, I wanted to cut them down to something like below.
[0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0]
// to
[0x4,2,7,3x2,0x9]
I wrote a couple one-liners (utilising Lodash' _.repeat) to convert to and from this pattern, however converting to doesn't seem to work in most/all cases.
let serialized = array.toString().replace(/((?:(\d)+,?)((?:\2+,?){2,}))/g, (m, p1, p2) => p2 + 'x' + m.replace(/,/g, '').length);
let parsed = serialized.replace(/(\d+)x(\d+),?/g, (z, p1, p2) => _.repeat(p1 + ',', +p2)).split(',');
I don't know why it doesn't work. It may be due to some of the numbers in the array. Eye-balling, the largest one is 4294967295, however well over 90% is just 0.
What am I missing in my RegEx that's preventing it from working correctly? Is there a simpler way that I'm too blind to see?
I'm fairly confident with converting it back from the serialized state, just need a hand getting it to the state.
Straight forward and simple serialization:
let serialize = arr => {
const elements = [];
const counts = []
let last = undefined;
[0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0].forEach((el,i,arr)=>{
if (el!==last) {
elements.push(el);
counts.push(1);
} else {
counts[counts.length-1]++;
}
last = el;
})
return elements.map((a,i)=>counts[i]>1?`${a}x${counts[i]}`:a).join(",");
};
console.log(serialize([0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0]));
UPDATE
Pure functional serialize one:
let serialize = arr => arr
.reduce((memo, element, i) => {
if (element !== arr[i - 1]) {
memo.push({count: 1, element});
} else {
memo[memo.length - 1].count++;
}
return memo;
},[])
.map(({count, element}) => count > 1 ? `${count}x${element}` : element)
.join(",");
console.log(serialize([0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0]));
Pure functional deserialize:
const deserialize = str => str
.split(",")
.map(c => c.split("x").reverse())
.reduce((memo, [el, count = 1]) => memo.concat(Array(+count).fill(+el)), []);
console.log(deserialize("4x0,2,7,2x3,9x0"))
In order to avoid using .reverse() in this logic, I'd recommend to change serialization from 4x0 to 0x4
Try this
var arr = [0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0];
var finalArray = []; //array into which count of values will go
var currentValue = ""; //current value for comparison
var tmpArr = []; //temporary array to hold values
arr.forEach( function( val, index ){
if ( val != currentValue && currentValue !== "" )
{
finalArray.push( tmpArr.length + "x" + tmpArr[0] );
tmpArr = [];
}
tmpArr.push(val);
currentValue = val;
});
finalArray.push( tmpArr.length + "x" + tmpArr[0] );
console.log(finalArray);
Another version without temporary array
var arr = [0, 0, 0, 0, 2, 7, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0];
var finalArray = []; //array into which count of values will go
var tmpCount = 0; //temporary variable to hold count
arr.forEach(function(val, index) {
if ( (val != arr[ index - 1 ] && index !== 0 ) )
{
finalArray.push(tmpCount + "x" + arr[ index - 1 ] );
tmpCount = 0;
}
tmpCount++;
if ( index == arr.length - 1 )
{
finalArray.push(tmpCount + "x" + arr[ index - 1 ] );
}
});
console.log(finalArray);
Do not use RegEx. Just use regular logic. I recommend array.reduce for this job.
const arr1 = [0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0]
const arr2 = ['0x4','2','7','3x2','0x9'];
const compact = arr => {
const info = arr.reduce((c, v) =>{
if(c.prevValue !== v){
c.order.push(v);
c.count[v] = 1;
c.prevCount = 1;
c.prevValue = v;
} else {
c.prevCount = c.prevCount + 1;
c.count[v] = c.count[v] + 1;
};
return c;
},{
prevValue: null,
prevCount: 0,
count: {},
order: []
});
return info.order.map(v => info.count[v] > 1 ? `${v}x${info.count[v]}` : `${v}`);
}
const expand = arr => {
return arr.reduce((c, v) => {
const split = v.split('x');
const value = +split[0];
const count = +split[1] || 1;
Array.prototype.push.apply(c, Array(count).fill(value));
return c;
}, []);
}
console.log(compact(arr1));
console.log(expand(arr2));
This is a typical reducing job. Here is your compress function done in just O(n) time..
var arr = [0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0],
compress = a => a.reduce((r,e,i,a) => e === a[i-1] ? (r[r.length-1][1]++,r) : (r.push([e,1]) ,r),[]);
console.log(JSON.stringify(compress(arr)));
since the motivation here is to reduce the size of the stored arrays, consider using something like gzip-js to compress your data.

Categories