How to refactor if blocks with repeated conditions? - javascript

Let's say I have some 6 conditions (a-f) and if statements listed below. As you can see there is a pattern for these if statements. Every next if statement has almost the same conditions as the previous one but the first condition which was used previously is removed.
if(a && b && c && d && e && f)
return 5;
if(b && c && d && e && f)
return 4;
if(c && d && e && f)
return 3;
if(d && e && f)
return 2;
if(e && f)
return 1;
if (f)
return 0;
return -1;
I think there is no need to have so many elaborate conditions, especially as there are cases which introduce some redundant condition checks and I would prefer to avoid that. I've tried to rewrite it, but I've only come up with something like this:
let res = -1;
if(f) {
res = 0;
if(e) {
res = 1;
if(d) {
res = 2;
if(c) {
res = 3;
} if(b) {
res = 4;
if(a) {
res = 5;
}
}
}
}
}
return res;
This way I no longer have redundant condition checks, but this solution still looks overcomplicated. Are there some clever ways to rewrite it so that I can have a simple solution without redundant checks?

Your nested if statements aren't that bad - they get rid of the duplication quite efficiently, and create a linear structure! You can further shorten the code by using a loop:
let res = -1;
for (const flag of [f, e, d, c, b, a]) {
if (flag) res++;
else break;
}
return res;
This still evaluates all the conditions up-front, so if they are costly to compute you might want to use functions that you can evaluate when needed:
let res = -1;
for (const getFlag of [()=>f, ()=>e, ()=>d, ()=>c, ()=>b, ()=>a]) {
if (getFlag()) res++;
else break;
}
return res;
If the conditions are cheap, and you also know that if one is false all the remaining ones are false as well, you don't even need to break the loop. It can then be simplified into a single expression:
return -1 + f + e + d + c + b + a;

Put the conditions into an array, along with the results.
const conds = [[a, 5], [b, 4], [c, 3], [d, 2], [e, 1], [f, 0]];
while (conds.length) {
if (conds.every(([cond]) => cond)) {
return conds[1];
}
conds.shift();
}
return -1;

Related

Calculating second largest number in array (Javascript) : Seems to work in Sandbox but fails Hackerrank testing

Tried to determine the second largest number in an array (Javascript) on CodeSandbox. It seems to work fine, but it fails the CodeWars testing. I have added a dummy array just to run my own tests in Sandbox.(Have mercy, I'm a beginner and this is my first StackOverFlow question)
const nums = [3, 100.3, 88, 1, -2.4, 9, 18];
const getSecondLargest = (nums) => {
const descending = nums.sort((a, b) => b - a);
return descending[1];
};
console.log(getSecondLargest(nums)); // console returns 88
EDIT: Okay so I with my super-tired brain I said CodeWars, when I actually meant Hackerrank (so sorry!). I realized they didn't necessarily test with NaNs, but they did have repeating numbers, so using the index of [1] isn't ideal. The exercise is from the 10 Days of Javascript - Day 3: Arrays https://hackerrank.com/domains/tutorials/10-days-of-javascript
So I now tried this code below, and it passes...but my code seems a bit janky, is there a cleaner way to write this, and can I combine it with the isNan logic then?
const nums = [3, 100, 88, 100, -2.4, 9, 18];
const getSecondLargest = (nums) => {
const ascending = nums.sort((a, b) => a - b);
if (ascending[ascending.length - 2] === ascending[ascending.length - 1]) {
return ascending[ascending.length - 3];
} else {
return ascending[ascending.length - 2];
}
};
console.log(getSecondLargest(nums)); // console returns 88
It looks like there maybe strings in the array and you need to handle that. Here are a few ways:
One is to filter the non-numerical stuff out before sorting. You can use isNaN() to test if an object "is not a number".
const getSecondLargest = (nums) => {
const descending = nums
.filter(n => !isNaN(n))
.sort((a, b) => b - a);
return descending.length < 2 ? undefined : descending[1];
};
Another option is to handle the strings in sorting. Push them to the end of the array:
const getSecondLargest = (nums) => {
const descending = nums.sort((a, b) => {
if (isNaN(a) && isNaN(b)) return 0;
if (isNaN(a)) return 1;
if (isNaN(b)) return -1;
return b - a;
});
return descending.length < 2 || isNaN(descending[1]) ? undefined : descending[1];
};
A third way is a simple for loop that keeps track of the 2 highest values:
const getSecondLargest = (nums) => {
let max1 = undefined;
let max2 = undefined;
for (let n of nums) {
if (isNaN(n)) continue;
if (max2 === undefined || n > max2) {
if (max1 === undefined || n > max1 ) {
max2 = max1;
max1 = n;
}
else {
max2 = n;
}
}
}
return max2;
}

Get minimum number without a Math function

I need to get a minimum number without using Math.Min function.
The function I've written is quite voluminous, how can I shortened the code and make it more compact?
Feel free to offer other solutions!
Here is my code:
function getMin(a,b,c) {
if (a > b) {
if (b > c) {
return c;
} else {
return b;
}
} else {
if (a > c) {
return c;
} else {
return a;
}
}
}
You could take a nested conditional structure.
function getMin(a, b, c) {
return a > b
? b > c ? c : b
: a > c ? c : a;
}
console.log(getMin(1, 2, 3));
console.log(getMin(1, 3, 2));
console.log(getMin(2, 1, 3));
console.log(getMin(2, 3, 1));
console.log(getMin(3, 1, 2));
console.log(getMin(3, 2, 1));
You might use reduce method with any number of arguments:
function getMin(...args) {
return args.reduce((r, a) => r < a ? r : a, Number.MAX_SAFE_INTEGER)
}
console.log(getMin(-12, 45, 765))
console.log(getMin(-12, 45, 76, 5, 13, -123))
Or do the same using the oldschool for loop:
function getMin() {
var min = Number.MAX_SAFE_INTEGER;
for (var i in arguments)
if (min > arguments[i]) min = arguments[i];
return min;
}
console.log(getMin(-12, 45, 765))
console.log(getMin(-12, 45, 76, 5, 13, -123))
Similar to yours but more compact, you can merge that if statements:
function(a,b,c){
if (a>b && a>c) return a;
if (b>a && b>a) return b;
if (c>a && c>b) return c;
return a; // if they are equal
}
This doesn't scale well with more numbers. As I said, it is just compact version of yours.
Very Easy
function getMin(a,b,c) {
var min = arguments[0];
for (var i = 0, j = arguments.length; i < j; i++){
if (arguments[i] < min) {
min = arguments[i];
}
}
return min;
}
getMin(12,3,5,0,-8)
Demo
There are multiple ways to do so (I have just shared two example)
Here is the working example:
https://codebrace.com/editor/b0884a856
i. Using reduce
function getmin(numarray){
return numarray.reduce((a, b) => ((a > b)?b:a));
}
ii. using sort
function getmin(numarray){
return numarray.sort()[0];
}

Getting range numbers using recursion in JavaScript

I am trying to get the range of numbers using recursion. Can someone explain to me why it isn't working?
function range(x,y){
var results = [];
if(x === y){
return results;
}
return results.push(range(x + 1,y));
}
range(1,5);
The beauty of recursion is that you don't need local variables (var results). You just pass state as arguments to each recursive iteration:
const concat = (xs, y) => xs.concat(y);
const range = (x, y) => {
const rec = (x, y, acc) => x < y ? rec(x + 1, y, concat(acc, x)) : acc;
return rec(x, y, []);
}
ES5 version in case you aren't familiar with the arrow syntax:
function concat(xs, y) {
return xs.concat(y);
}
function range(x, y) {
function rec(x, y, acc) {
return x < y ? rec(x + 1, y, concat(acc, x)) : acc;
}
return rec(x, y, []);
}
That isn't the most elegant solution though!
With recursion we can simply build up the stack with each recursive call. Each stack frame contains a computed partial result. Then we just need to unwind the stack and attach each partial result to an array:
const range = (x, y) => x < y ? [x].concat(range(x + 1, y)) : [];
Or more functional:
const concat = (xs, y) => xs.concat(y);
const range = (x, y) => x < y ? concat([x], range(x + 1, y)) : [];
Note that concat([x], range(x + 1, y)) is the recursive case and [] the base case.
Try this:
function rangeOfNumbers(startNum, endNum) {
if (startNum - endNum === 0) {
return [startNum];
} else {
const numbers = rangeOfNumbers(startNum + 1, endNum);
numbers.unshift(startNum);
return numbers;
}
};
Solution:
Solved this recursion problem, which is taking 2 numbers as input and returning back the array which contains range of the numbers inclusive of the startNumber and EndNumber
Assumption-> end_num is always greater than start_num
function rangeOfNumbers(start_num, end_num) {
if(start_num!==end_num){
let numbers = rangeOfNumbers(start_num+1,end_num);
numbers.unshift(start_num);
return numbers;
}
else
return [start_num];
};
Results will be always empty since you actually don't put anything in it.
What would work is this
function range(x,y){
var results = [];
if(x === y){
return results;
}
results.push(x);
return results.concat(range(x + 1,y));
}
range(1,5);
Let's firstly try to answer your "why" question before we give a solution because none of these answers explain your "why" question.
When you return results.push(<any argument>) the return value is the length of the array after the push. On the first call in your example, x does not equal y, so we are returning the call to push. You can think of it like this:
return array.push(<anything>) is going to be the same as:
array.push(<anything>)
return array.length
Therefore, you will always return the number 1 from this because the length of the array when you push the function call to it is 1. The content of that array will be another array that's nested all the way to the n levels deep where n is the range, but it's length will still be one and you will never see the content of this array unless you did it this way:
results.push(rangeOfNumbers(x+1, y))
return results;
In your example rangeOfNumbers(1, 5), if you logged that return value it would look like this:
[ [ [ [ [ ] ] ] ] ]
I solved it this way, but I like the functional solution that was posted by another user more:
function rangeOfNumbers(s, e) {
return s == e ? [s] : [s, ...rangeOfNumbers(s+1, e)];
}
function rangeOfNumbers(startNum, endNum) {
if (startNum>endNum){
return [];
}
else{
const range = rangeOfNumbers(startNum+1, endNum);
range.unshift(startNum);
return range;
}
};
//or more simple
function rangeOfNumbers(startNum, endNum) {
return endNum>=startNum?rangeOfNumbers(startNum,endNum-1).concat(endNum):[];
};
function rangeOfNumbers(firstNum, lastNum) {
if (firstNum - lastNum === 0) {
return [lastNum];
} else {
let rangeArray = rangeOfNumbers(firstNum, lastNum - 1);
rangeArray.push(lastNum);
return rangeArray;
}
}
console.log(rangeOfNumbers(1, 5))
Lots of clever solutions posted, but I think this is a use case for the plain old 'for loop'. It's easier to see what's happening, and it will be easier for new devs on your team. My example is inclusive (it will include the min value and the max value), and it has an optional step parameter which will default to 1 if not passed in.
function range(min, max, step = 1) {
let arr = []
for (let i = min; i <= max; i = i + step ) {
arr.push(i)
}
return arr
}

How to early break reduce() method?

How can I break the iteration of reduce() method?
for:
for (var i = Things.length - 1; i >= 0; i--) {
if(Things[i] <= 0){
break;
}
};
reduce()
Things.reduce(function(memo, current){
if(current <= 0){
//break ???
//return; <-- this will return undefined to memo, which is not what I want
}
}, 0)
You CAN break on any iteration of a .reduce() invocation by mutating the 4th argument of the reduce function: "array". No need for a custom reduce function. See Docs for full list of .reduce() parameters.
Array.prototype.reduce((acc, curr, i, array))
The 4th argument is the array being iterated over.
const array = ['apple', '-pen', '-pineapple', '-pen'];
const x = array
.reduce((acc, curr, i, arr) => {
if(i === 2) arr.splice(1); // eject early
return acc += curr;
}, '');
console.log('x: ', x); // x: apple-pen-pineapple
WHY?:
The one and only reason I can think of to use this instead of the many other solutions presented is if you want to maintain a functional programming methodology to your algorithm, and you want the most declarative approach possible to accomplish that. If your entire goal is to literally REDUCE an array to an alternate non-falsey primitive (string, number, boolean, Symbol) then I would argue this IS in fact, the best approach.
WHY NOT?
There's a whole list of arguments to make for NOT mutating function parameters as it's a bad practice.
UPDATE
Some of the commentators make a good point that the original array is being mutated in order to break early inside the .reduce() logic.
Therefore, I've modified the answer slightly by adding a .slice(0) before calling a follow-on .reduce() step, yielding a copy of the original array.
NOTE: Similar ops that accomplish the same task are slice() (less explicit), and spread operator [...array] (slightly less performant). Bear in mind, all of these add an additional constant factor of linear time to the overall runtime ... + O(n).
The copy, serves to preserve the original array from the eventual mutation that causes ejection from iteration.
const array = ['apple', '-pen', '-pineapple', '-pen'];
const x = array
.slice(0) // create copy of "array" for iterating
.reduce((acc, curr, i, arr) => {
if (i === 2) arr.splice(1); // eject early by mutating iterated copy
return (acc += curr);
}, '');
console.log("x: ", x, "\noriginal Arr: ", array);
// x: apple-pen-pineapple
// original Arr: ['apple', '-pen', '-pineapple', '-pen']
Don't use reduce. Just iterate on the array with normal iterators (for, etc) and break out when your condition is met.
You can use functions like some and every as long as you don't care about the return value. every breaks when the callback returns false, some when it returns true:
things.every(function(v, i, o) {
// do stuff
if (timeToBreak) {
return false;
} else {
return true;
}
}, thisArg);
Edit
A couple of comments that "this doesn't do what reduce does", which is true, but it can. Here's an example of using every in a similar manner to reduce that returns as soon as the break condition is reached.
// Soruce data
let data = [0,1,2,3,4,5,6,7,8];
// Multiple values up to 5 by 6,
// create a new array and stop processing once
// 5 is reached
let result = [];
data.every(a => a < 5? result.push(a*6) : false);
console.log(result);
This works because the return value from push is the length of the result array after the new element has been pushed, which will always be 1 or greater (hence true), otherwise it returns false and the loop stops.
There is no way, of course, to get the built-in version of reduce to exit prematurely.
But you can write your own version of reduce which uses a special token to identify when the loop should be broken.
var EXIT_REDUCE = {};
function reduce(a, f, result) {
for (let i = 0; i < a.length; i++) {
let val = f(result, a[i], i, a);
if (val === EXIT_REDUCE) break;
result = val;
}
return result;
}
Use it like this, to sum an array but exit when you hit 99:
reduce([1, 2, 99, 3], (a, b) => b === 99 ? EXIT_REDUCE : a + b, 0);
> 3
Array.every can provide a very natural mechanism for breaking out of high order iteration.
const product = function(array) {
let accumulator = 1;
array.every( factor => {
accumulator *= factor;
return !!factor;
});
return accumulator;
}
console.log(product([2,2,2,0,2,2]));
// 0
You can break every code - and thus every build in iterator - by throwing an exception:
function breakReduceException(value) {
this.value = value
}
try {
Things.reduce(function(memo, current) {
...
if (current <= 0) throw new breakReduceException(memo)
...
}, 0)
} catch (e) {
if (e instanceof breakReduceException) var memo = e.value
else throw e
}
You can use try...catch to exit the loop.
try {
Things.reduce(function(memo, current){
if(current <= 0){
throw 'exit loop'
//break ???
//return; <-- this will return undefined to memo, which is not what I want
}
}, 0)
} catch {
// handle logic
}
As the promises have resolve and reject callback arguments, I created the reduce workaround function with the break callback argument. It takes all the same arguments as native reduce method, except the first one is an array to work on (avoid monkey patching). The third [2] initialValue argument is optional. See the snippet below for the function reducer.
var list = ["w","o","r","l","d"," ","p","i","e","r","o","g","i"];
var result = reducer(list,(total,current,index,arr,stop)=>{
if(current === " ") stop(); //when called, the loop breaks
return total + current;
},'hello ');
console.log(result); //hello world
function reducer(arr, callback, initial) {
var hasInitial = arguments.length >= 3;
var total = hasInitial ? initial : arr[0];
var breakNow = false;
for (var i = hasInitial ? 0 : 1; i < arr.length; i++) {
var currentValue = arr[i];
var currentIndex = i;
var newTotal = callback(total, currentValue, currentIndex, arr, () => breakNow = true);
if (breakNow) break;
total = newTotal;
}
return total;
}
And here is the reducer as an Array method modified script:
Array.prototype.reducer = function(callback,initial){
var hasInitial = arguments.length >= 2;
var total = hasInitial ? initial : this[0];
var breakNow = false;
for (var i = hasInitial ? 0 : 1; i < this.length; i++) {
var currentValue = this[i];
var currentIndex = i;
var newTotal = callback(total, currentValue, currentIndex, this, () => breakNow = true);
if (breakNow) break;
total = newTotal;
}
return total;
};
var list = ["w","o","r","l","d"," ","p","i","e","r","o","g","i"];
var result = list.reducer((total,current,index,arr,stop)=>{
if(current === " ") stop(); //when called, the loop breaks
return total + current;
},'hello ');
console.log(result);
Reduce functional version with break can be implemented as 'transform', ex. in underscore.
I tried to implement it with a config flag to stop it so that the implementation reduce doesn't have to change the data structure that you are currently using.
const transform = (arr, reduce, init, config = {}) => {
const result = arr.reduce((acc, item, i, arr) => {
if (acc.found) return acc
acc.value = reduce(config, acc.value, item, i, arr)
if (config.stop) {
acc.found = true
}
return acc
}, { value: init, found: false })
return result.value
}
module.exports = transform
Usage1, simple one
const a = [0, 1, 1, 3, 1]
console.log(transform(a, (config, acc, v) => {
if (v === 3) { config.stop = true }
if (v === 1) return ++acc
return acc
}, 0))
Usage2, use config as internal variable
const pixes = Array(size).fill(0)
const pixProcessed = pixes.map((_, pixId) => {
return transform(pics, (config, _, pic) => {
if (pic[pixId] !== '2') config.stop = true
return pic[pixId]
}, '0')
})
Usage3, capture config as external variable
const thrusts2 = permute([9, 8, 7, 6, 5]).map(signals => {
const datas = new Array(5).fill(_data())
const ps = new Array(5).fill(0)
let thrust = 0, config
do {
config = {}
thrust = transform(signals, (_config, acc, signal, i) => {
const res = intcode(
datas[i], signal,
{ once: true, i: ps[i], prev: acc }
)
if (res) {
[ps[i], acc] = res
} else {
_config.stop = true
}
return acc
}, thrust, config)
} while (!config.stop)
return thrust
}, 0)
You cannot break from inside of a reduce method. Depending on what you are trying to accomplish you could alter the final result (which is one reason you may want to do this)
const result = [1, 1, 1].reduce((a, b) => a + b, 0); // returns 3
console.log(result);
const result = [1, 1, 1].reduce((a, b, c, d) => {
if (c === 1 && b < 3) {
return a + b + 1;
}
return a + b;
}, 0); // now returns 4
console.log(result);
Keep in mind: you cannot reassign the array parameter directly
const result = [1, 1, 1].reduce( (a, b, c, d) => {
if (c === 0) {
d = [1, 1, 2];
}
return a + b;
}, 0); // still returns 3
console.log(result);
However (as pointed out below), you CAN affect the outcome by changing the array's contents:
const result = [1, 1, 1].reduce( (a, b, c, d) => {
if (c === 0) {
d[2] = 100;
}
return a + b;
}, 0); // now returns 102
console.log(result);
Providing you do not need to return an array, perhaps you could use some()?
Use some instead which auto-breaks when you want. Send it a this accumulator. Your test and accumulate function cannot be an arrow function as their this is set when the arrow function is created.
const array = ['a', 'b', 'c', 'd', 'e'];
var accum = {accum: ''};
function testerAndAccumulator(curr, i, arr){
this.tot += arr[i];
return curr==='c';
};
accum.tot = "";
array.some(testerAndAccumulator, accum);
var result = accum.tot;
In my opinion this is the better solution to the accepted answer provided you do not need to return an array (eg in a chain of array operators), as you do not alter the original array and you do not need to make a copy of it which could be bad for large arrays.
So, to terminate even earlier the idiom to use would be arr.splice(0).
Which prompts the question, why can't one just use arr = [] in this case?
I tried it and the reduce ignored the assignment, continuing on unchanged.
The reduce idiom appears to respond to forms such as splice but not forms such as the assignment operator??? - completely unintuitive - and has to be rote-learnt as precepts within the functional programming credo ...
const array = ['9', '91', '95', '96', '99'];
const x = array
.reduce((acc, curr, i, arr) => {
if(i === 2) arr.splice(1); // eject early
return acc += curr;
}, '');
console.log('x: ', x); // x: 99195
The problem is, that inside of the accumulator it is not possible to just stop the whole process. So by design something in the outer scope must be manipulated, which always leads to a necessary mutation.
As many others already mentioned throw with try...catch is not really an approach which can be called "solution". It is more a hack with many unwanted side effects.
The only way to do this WITHOUT ANY MUTATIONS is by using a second compare function, which decides whether to continue or stop. To still avoid a for-loop, it has to be solved with a recursion.
The code:
function reduceCompare(arr, cb, cmp, init) {
return (function _(acc, i) {
return i < arr.length && cmp(acc, arr[i], i, arr) === true ? _(cb(acc, arr[i], i, arr), i + 1) : acc;
})(typeof init !== 'undefined' ? init : arr[0], 0);
}
This can be used like:
var arr = ['a', 'b', 'c', 'd'];
function join(acc, curr) {
return acc + curr;
}
console.log(
reduceCompare(
arr,
join,
function(acc) { return acc.length < 1; },
''
)
); // logs 'a'
console.log(
reduceCompare(
arr,
join,
function(acc, curr) { return curr !== 'c'; },
''
)
); // logs 'ab'
console.log(
reduceCompare(
arr,
join,
function(acc, curr, i) { return i < 3; },
''
)
); // logs 'abc'
I made an npm library out of this, also containing a TypeScript and ES6 version. Feel free to use it:
https://www.npmjs.com/package/array-reduce-compare
or on GitHub:
https://github.com/StefanJelner/array-reduce-compare
You could to write your own reduce method. Invoking it like this, so it follows same logic and you control your own escape / break solution. It retains functional style and allows breaking.
const reduce = (arr, fn, accum) => {
const len = arr.length;
let result = null;
for(let i = 0; i < len; i=i+1) {
result = fn(accum, arr[i], i)
if (accum.break === true) {
break;
}
}
return result
}
const arr = ['a', 'b', 'c', 'shouldnotgethere']
const myResult = reduce(arr, (accum, cur, ind) => {
accum.result = accum.result + cur;
if(ind === 2) {
accum.break = true
}
return accum
}, {result:'', break: false}).result
console.log({myResult})
Or create your own reduce recursion method:
const rcReduce = (arr, accum = '', ind = 0) => {
const cur = arr.shift();
accum += cur;
const isBreak = ind > 1
return arr.length && !isBreak ? rcReduce(arr, accum, ind + 1) : accum
}
const myResult = rcReduce(['a', 'b', 'c', 'shouldngethere'])
console.log({myResult})
Another simple implementation that I came with solving the same issue:
function reduce(array, reducer, first) {
let result = first || array.shift()
while (array.length > 0) {
result = reducer(result, array.shift())
if (result && result.reduced) {
return result.reduced
}
}
return result
}
If you want to chain promises sequentially with reduce using the pattern below:
return [1,2,3,4].reduce(function(promise,n,i,arr){
return promise.then(function(){
// this code is executed when the reduce loop is terminated,
// so truncating arr here or in the call below does not works
return somethingReturningAPromise(n);
});
}, Promise.resolve());
But need to break according to something happening inside or outside a promise
things become a little bit more complicated because the reduce loop is terminated before the first promise is executed, making truncating the array in the promise callbacks useless, I ended up with this implementation:
function reduce(array, promise, fn, i) {
i=i||0;
return promise
.then(function(){
return fn(promise,array[i]);
})
.then(function(result){
if (!promise.break && ++i<array.length) {
return reduce(array,promise,fn,i);
} else {
return result;
}
})
}
Then you can do something like this:
var promise=Promise.resolve();
reduce([1,2,3,4],promise,function(promise,val){
return iter(promise, val);
}).catch(console.error);
function iter(promise, val) {
return new Promise(function(resolve, reject){
setTimeout(function(){
if (promise.break) return reject('break');
console.log(val);
if (val==3) {promise.break=true;}
resolve(val);
}, 4000-1000*val);
});
}
I solved it like follows, for example in the some method where short circuiting can save a lot:
const someShort = (list, fn) => {
let t;
try {
return list.reduce((acc, el) => {
t = fn(el);
console.log('found ?', el, t)
if (t) {
throw ''
}
return t
}, false)
} catch (e) {
return t
}
}
const someEven = someShort([1, 2, 3, 1, 5], el => el % 2 === 0)
console.log(someEven)
UPDATE
Away more generic answer could be something like the following
const escReduce = (arr, fn, init, exitFn) => {
try {
return arr.reduce((...args) => {
if (exitFn && exitFn(...args)) {
throw args[0]
}
return fn(...args)
}, init)
} catch(e){ return e }
}
escReduce(
Array.from({length: 100}, (_, i) => i+1),
(acc, e, i) => acc * e,
1,
acc => acc > 1E9
); // 6227020800
give we pass an optional exitFn which decides to break or not

Compare arrays as (multi-) sets

I'm looking for an efficient way to find out whether two arrays contain same amounts of equal elements (in the == sense), in any order:
foo = {/*some object*/}
bar = {/*some other object*/}
a = [1,2,foo,2,bar,2]
b = [bar,2,2,2,foo,1]
sameElements(a, b) --> true
PS. Note that pretty much every solution in the thread uses === and not == for comparison. This is fine for my needs though.
Update 5
I posted a new answer with a different approach.
Update
I extended the code to have the possibility of either checking by reference or equality
just pass true as second parameter to do a reference check.
Also I added the example to Brunos JSPerf
It runs at about 11 ops/s doing a reference check
I will comment the code as soon(!) as I get some spare time to explain it a bit more, but at the moment don't have the time for that, sry. Done
Update 2.
Like Bruno pointed out in the comments sameElements([NaN],[NaN]) yields false
In my opinion this is the correct behaviour as NaN is ambigious and should always lead to a false result,at least when comparing NaN.equals(NaN). But he had quite a good point.
Whether
[1,2,foo,bar,NaN,3] should be equal to [1,3,foo,NaN,bar,2] or not.
Ok.. honestly I'm a bit torn whether it should or not, so i added two flags.
Number.prototype.equal.NaN
If true
NaN.equals(NaN) //true
Array.prototype.equal.NaN
If true
[NaN].equals([NaN],true) //true
note this is only for reference checks. As a deep check would invoke Number.prototype.equals anyway
Update 3:
Dang i totally missed 2 lines in the sort function.
Added
r[0] = a._srt; //DANG i totally missed this line
r[1] = b._srt; //And this.
Line 105 in the Fiddle
Which is kind of important as it determines the consistent order of the Elements.
Update 4
I tried to optimize the sort function a bit, and managed to get it up to about 20 ops/s.
Below is the updated code, as well as the updated fiddle =)
Also i chose to mark the objects outside the sort function, it doesn't seem to make a performance difference anymore, and its more readable
Here is an approach using Object.defineProperty to add equals functions to
Array,Object,Number,String,Boolean's prototype to avoid typechecking in one function for
performance reasons. As we can recursively call .equals on any element.
But of course checking Objects for equality may cause performance issues in big Objects.
So if anyone feels unpleasant manipulating native prototypes, just do a type check and put it into one function
Object.defineProperty(Boolean.prototype, "equals", {
enumerable: false,
configurable: true,
value: function (c) {
return this == c; //For booleans simply return the equality
}
});
Object.defineProperty(Number.prototype, "equals", {
enumerable: false,
configurable: true,
value: function (c) {
if (Number.prototype.equals.NaN == true && isNaN(this) && c != c) return true; //let NaN equals NaN if flag set
return this == c; // else do a normal compare
}
});
Number.prototype.equals.NaN = false; //Set to true to return true for NaN == NaN
Object.defineProperty(String.prototype, "equals", {
enumerable: false,
configurable: true,
value: Boolean.prototype.equals //the same (now we covered the primitives)
});
Object.defineProperty(Object.prototype, "equals", {
enumerable: false,
configurable: true,
value: function (c, reference) {
if (true === reference) //If its a check by reference
return this === c; //return the result of comparing the reference
if (typeof this != typeof c) {
return false; //if the types don't match (Object equals primitive) immediately return
}
var d = [Object.keys(this), Object.keys(c)],//create an array with the keys of the objects, which get compared
f = d[0].length; //store length of keys of the first obj (we need it later)
if (f !== d[1].length) {//If the Objects differ in the length of their keys
return false; //immediately return
}
for (var e = 0; e < f; e++) { //iterate over the keys of the first object
if (d[0][e] != d[1][e] || !this[d[0][e]].equals(c[d[1][e]])) {
return false; //if either the key name does not match or the value does not match, return false. a call of .equal on 2 primitives simply compares them as e.g Number.prototype.equal gets called
}
}
return true; //everything is equal, return true
}
});
Object.defineProperty(Array.prototype, "equals", {
enumerable: false,
configurable: true,
value: function (c,reference) {
var d = this.length;
if (d != c.length) {
return false;
}
var f = Array.prototype.equals.sort(this.concat());
c = Array.prototype.equals.sort(c.concat(),f)
if (reference){
for (var e = 0; e < d; e++) {
if (f[e] != c[e] && !(Array.prototype.equals.NaN && f[e] != f[e] && c[e] != c[e])) {
return false;
}
}
} else {
for (var e = 0; e < d; e++) {
if (!f[e].equals(c[e])) {
return false;
}
}
}
return true;
}
});
Array.prototype.equals.NaN = false; //Set to true to allow [NaN].equals([NaN]) //true
Object.defineProperty(Array.prototype.equals,"sort",{
enumerable:false,
value:function sort (curr,prev) {
var weight = {
"[object Undefined]":6,
"[object Object]":5,
"[object Null]":4,
"[object String]":3,
"[object Number]":2,
"[object Boolean]":1
}
if (prev) { //mark the objects
for (var i = prev.length,j,t;i>0;i--) {
t = typeof (j = prev[i]);
if (j != null && t === "object") {
j._pos = i;
} else if (t !== "object" && t != "undefined" ) break;
}
}
curr.sort (sorter);
if (prev) {
for (var k = prev.length,l,t;k>0;k--) {
t = typeof (l = prev[k]);
if (t === "object" && l != null) {
delete l._pos;
} else if (t !== "object" && t != "undefined" ) break;
}
}
return curr;
function sorter (a,b) {
var tStr = Object.prototype.toString
var types = [tStr.call(a),tStr.call(b)]
var ret = [0,0];
if (types[0] === types[1] && types[0] === "[object Object]") {
if (prev) return a._pos - b._pos
else {
return a === b ? 0 : 1;
}
} else if (types [0] !== types [1]){
return weight[types[0]] - weight[types[1]]
}
return a>b?1:a<b?-1:0;
}
}
});
With this we can reduce the sameElements function to
function sameElements(c, d,referenceCheck) {
return c.equals(d,referenceCheck); //call .equals of Array.prototype.
}
Note. of course you could put all equal functions into the sameElements function, for the cost of the typechecking.
Now here are 3 examples: 1 with deep checking, 2 with reference checking.
var foo = {
a: 1,
obj: {
number: 2,
bool: true,
string: "asd"
},
arr: [1, 2, 3]
};
var bar = {
a: 1,
obj: {
number: 2,
bool: true,
string: "asd"
},
arr: [1, 2, 3]
};
var foobar = {
a: 1,
obj: {
number: 2,
bool: true,
string: "asd"
},
arr: [1, 2, 3, 4]
};
var a = [1, 2, foo, 2, bar, 2];
var b = [foo, 2, 2, 2, bar, 1];
var c = [bar, 2, 2, 2, bar, 1];
So these are the Arrays we compare. And the output is
Check a and b with references only.
console.log (sameElements ( a,b,true)) //true As they contain the same elements
Check b and c with references only
console.log (sameElements (b,c,true)) //false as c contains bar twice.
Check b and c deeply
console.log (sameElements (b,c,false)) //true as bar and foo are equal but not the same
Check for 2 Arrays containing NaN
Array.prototype.equals.NaN = true;
console.log(sameElements([NaN],[NaN],true)); //true.
Array.prototype.equals.NaN = false;
Demo on JSFiddle
You can implement the following algorithm:
If a and b do not have the same length:
Return false.
Otherwise:
Clone b,
For each item in a:
If the item exists in our clone of b:
Remove the item from our clone of b,
Otherwise:
Return false.
Return true.
With Javascript 1.6, you can use every() and indexOf() to write:
function sameElements(a, b)
{
if (a.length != b.length) {
return false;
}
var ourB = b.concat();
return a.every(function(item) {
var index = ourB.indexOf(item);
if (index < 0) {
return false;
} else {
ourB.splice(index, 1);
return true;
}
});
}
Note this implementation does not completely fulfill your requirements because indexOf() uses strict equality (===) internally. If you really want non-strict equality (==), you will have to write an inner loop instead.
Like this perhaps?
var foo = {}; var bar=[];
var a = [3,2,1,foo]; var b = [foo,1,2,3];
function comp(a,b)
{
// immediately discard if they are of different sizes
if (a.length != b.length) return false;
b = b.slice(0); // clone to keep original values after the function
a.forEach(function(e) {
var i;
if ((i = b.indexOf(e)) != -1)
b.splice(i, 1);
});
return !b.length;
}
comp(a,b);
UPDATE
As #Bergi and #thg435 point out my previous implementation was flawed so here is another implementation:
function sameElements(a, b) {
var objs = [];
// if length is not the same then must not be equal
if (a.length != b.length) return false;
// do an initial sort which will group types
a.sort();
b.sort();
for ( var i = 0; i < a.length; i++ ) {
var aIsPrimitive = isPrimitive(a[i]);
var bIsPrimitive = isPrimitive(b[i]);
// NaN will not equal itself
if( a[i] !== a[i] ) {
if( b[i] === b[i] ) {
return false;
}
}
else if (aIsPrimitive && bIsPrimitive) {
if( a[i] != b[i] ) return false;
}
// if not primitive increment the __count property
else if (!aIsPrimitive && !bIsPrimitive) {
incrementCountA(a[i]);
incrementCountB(b[i]);
// keep track on non-primitive objects
objs.push(i);
}
// if both types are not the same then this array
// contains different number of primitives
else {
return false;
}
}
var result = true;
for (var i = 0; i < objs.length; i++) {
var ind = objs[i];
// if __aCount and __bCount match then object exists same
// number of times in both arrays
if( a[ind].__aCount !== a[ind].__bCount ) result = false;
if( b[ind].__aCount !== b[ind].__bCount ) result = false;
// revert object to what it was
// before entering this function
delete a[ind].__aCount;
delete a[ind].__bCount;
delete b[ind].__aCount;
delete b[ind].__bCount;
}
return result;
}
// inspired by #Bergi's code
function isPrimitive(arg) {
return Object(arg) !== arg;
}
function incrementCountA(arg) {
if (arg.hasOwnProperty("__aCount")) {
arg.__aCount = arg.__aCount + 1;
} else {
Object.defineProperty(arg, "__aCount", {
enumerable: false,
value: 1,
writable: true,
configurable: true
});
}
}
function incrementCountB(arg) {
if (arg.hasOwnProperty("__bCount")) {
arg.__bCount = arg.__bCount + 1;
} else {
Object.defineProperty(arg, "__bCount", {
enumerable: false,
value: 1,
writable: true,
configurable: true
});
}
}
Then just call the function
sameElements( ["NaN"], [NaN] ); // false
// As "1" == 1 returns true
sameElements( [1],["1"] ); // true
sameElements( [1,2], [1,2,3] ); //false
The above implement actually defines a new property called "__count" that is used to keep track of non-primitive elements in both arrays. These are deleted before the function returns so as to leave the array elements as before.
Fiddle here
jsperf here.
The reason I changed the jsperf test case was that as #Bergi states the test arrays, especially the fact there were only 2 unique objects in the whole array is not representative of what we are testing for.
One other advantage of this implementation is that if you need to make it compatible with pre IE9 browsers instead of using the defineProperty to create a non-enumerable property you could just use a normal property.
Thanks everyone for sharing ideas! I've came up with the following
function sameElements(a, b) {
var hash = function(x) {
return typeof x + (typeof x == "object" ? a.indexOf(x) : x);
}
return a.map(hash).sort().join() == b.map(hash).sort().join();
}
This isn't the fastest solution, but IMO, most readable one so far.
i wasn't sure if "===" is ok, the question is a bit vauge...
if so, this is quite a bit faster and simpler than some other possible ways of doing it:
function isSame(a,b){
return a.length==b.length &&
a.filter(function(a){ return b.indexOf(a)!==-1 }).length == b.length;
}
Edit 2
1) Thanks to user2357112 for pointing out the Object.prototype.toString.call issue
this also showed, the reason it was that fast, that it didn't consider Arrays ...
I fixed the code,it should be working now :), unfortunately its now at about 59ops/s on chrome and 45ops/s on ff.
Fiddle and JSPerf is updated.
Edit
1)
I fixed the code, it supports mutliple variables referencing the same Object now.
A little bit slower than before, but still over 100ops/s on chrome.
2)
I tried using a bitmask instead of an array to keep multiple positions of the objects, but its nearly 15ops/s slow
3) As pointed ot in the comments i forgot to reset tmp after [[get]] is called fixed the code, the fiddle, and the perf.
So thanks to user2357112 with his Answer heres another approach using counting
var sameElements = (function () {
var f, of, objectFlagName;
of = objectFlagName = "__pos";
var tstr = function (o) {
var t = typeof o;
if (o === null)
t = "null";
return t
};
var types = {};
(function () {
var tmp = {};
Object.defineProperty(types, tstr(1), {
set: function (v) {
if (f)
tmp[v] = -~tmp[v];
else
tmp[v] = ~-tmp[v];
},
get: function () {
var ret = 1;
for (var k in tmp) {
ret &= !tmp[k];
}
tmp = {};
return ret;
}
});
})();
(function () {
var tmp = {};
Object.defineProperty(types, tstr(""), {
set: function (v) {
if (f) {
tmp[v] = -~tmp[v];
} else {
tmp[v] = ~-tmp[v];
}
},
get: function () {
var ret = 1;
for (var k in tmp) {
ret &= !tmp[k];
}
tmp = {};
return ret;
}
});
})();
(function () {
var tmp = [];
function add (v) {
tmp.push(v);
if (v[of]===undefined) {
v[of] = [tmp.length -1];
} else {
v[of].push(tmp.length -1)
}
}
Object.defineProperty(types, tstr({}), {
get: function () {
var ret = true;
for (var i = tmp.length - 1; i >= 0; i--) {
var c = tmp[i]
if (typeof c !== "undefined") {
ret = false
delete c[of]
}
}
tmp = [];
return ret;
},
set: function (v) {
var pos;
if (f) {
add (v);
} else if (!f && (pos = v[of]) !== void 0) {
tmp[pos.pop()] = undefined;
if (pos.length === 0)
delete v[of];
} else {
add (v);
}
}
});
}());
(function () {
var tmp = 0;
Object.defineProperty(types, tstr(undefined), {
get: function () {
var ret = !tmp;
tmp = 0;
return ret;
},
set: function () {
tmp += f ? 1 : -1;
}
});
})();
(function () {
var tmp = 0;
Object.defineProperty(types, tstr(null), {
get: function () {
var ret = !tmp;
tmp = 0;
return ret;
},
set: function () {
tmp += f ? 1 : -1;
}
});
})();
var tIt = [tstr(1), tstr(""), tstr({}), tstr(undefined), tstr(null)];
return function eq(a, b) {
f = true;
for (var i = a.length - 1; i >= 0; i--) {
var v = a[i];
types[tstr(v)] = v;
}
f = false;
for (var k = b.length - 1; k >= 0; k--) {
var w = b[k];
types[tstr(w)] = w;
}
var r = 1;
for (var l = 0, j; j = tIt[l]; l++) {
r &= types [j]
}
return !!r;
}
})()
Here is a JSFiddle and a JSPerf (it uses the same Arrays a and b as in the previous answers perf) with this code vs the Closure compiled
Heres the output. note: it doesn't support a deep comparison anymore, as is
var foo = {a:2}
var bar = {a:1};
var a = [1, 2, foo, 2, bar, 2];
var b = [foo, 2, 2, 2, bar, 1];
var c = [bar, 2, 2, 2, bar, 1];
console.log(sameElements([NaN],[NaN])); //true
console.log (sameElements ( a,b)) //true
console.log (sameElements (b,c)) //false
Using efficient lookup tables for the counts of the elements:
function sameElements(a) { // can compare any number of arrays
var map, maps = [], // counting booleans, numbers and strings
nulls = [], // counting undefined and null
nans = [], // counting nans
objs, counts, objects = [],
al = arguments.length;
// quick escapes:
if (al < 2)
return true;
var l0 = a.length;
if ([].slice.call(arguments).some(function(s) { return s.length != l0; }))
return false;
for (var i=0; i<al; i++) {
var multiset = arguments[i];
maps.push(map = {}); // better: Object.create(null);
objects.push({vals: objs=[], count: counts=[]});
nulls[i] = 0;
nans[i] = 0;
for (var j=0; j<l0; j++) {
var val = multiset[j];
if (val !== val)
nans[i]++;
else if (val === null)
nulls[i]++;
else if (Object(val) === val) { // non-primitive
var ind = objs.indexOf(val);
if (ind > -1)
counts[ind]++;
else
objs.push(val), counts.push(1);
} else { // booleans, strings and numbers do compare together
if (typeof val == "boolean")
val = +val;
if (val in map)
map[val]++;
else
map[val] = 1;
}
}
}
// testing if nulls and nans are the same everywhere
for (var i=1; i<al; i++)
if (nulls[i] != nulls[0] || nans[i] != nans[0])
return false;
// testing if primitives were the same everywhere
var map0 = maps[0];
for (var el in map0)
for (var i=1; i<al; i++) {
if (map0[el] !== maps[i][el])
return false;
delete maps[i][el];
}
for (var i=1; i<al; i++)
for (var el in maps[i])
return false;
// testing if objects were the same everywhere
var objs0 = objects[0].vals,
ol = objs0.length;
counts0 = objects[0].count;
for (var i=1; i<al; i++)
if (objects[i].count.length != ol)
return false;
for (var i=0; i<ol; i++)
for (var j=1; j<al; j++)
if (objects[j].count[ objects[j].vals.indexOf(objs0[i]) ] != counts0[i])
return false;
// else, the multisets are equal:
return true;
}
It still uses indexOf search amongst all objects, so if you have multisets with many different objects you might want to optimize that part as well. Have a look at Unique ID or object signature (and it's duplicate questions) for how to get lookup table keys for them. And if you don't have many primitive values in the multisets, you might just store them in arrays and sort those before comparing each item-by-item (like #Bruno did).
Disclaimer: This solution doesn't try to get the [[PrimitiveValue]] of objects, they will never be counted as equal to primitives (while == would do).
Here is the update on #Bruno's jsperf test of the answers, yet I guess only two objects (each of them present 500 times in the 10k array) and no duplicate primitive values are not representative.

Categories