'Reduce array of arrays of booleans to single array of booleans (Array<Array<boolean>> to Array<boolean>)
I have an array containing a random number of arrays of the same length with booleans.
arrBefore = [arr1, arr2, ..., arrn];
I want to return a single array of booleans containing true if any of values for the index is true and false otherwise.
arrBefore = [[true, false, false], [false, false, false], [true, false, true], [true, false, true]];
arrAfter = reduceMyArr(arrBefore);
console.log(arrAfter);
//[true, false, true]
I know how to do it with for loops. But I want to do it with map() and reduce() instead. I couldn't find a solution on stack. Any help would be greatly appreciated.
update 1
My example was poorly choosen since it led to some confusion in the awnsers. I want to compare the inner arrays indexes. So the result should be an array of the same lenght of the inner arrays. And arrAfter[i] should be true if arr1[i], arr2[i], ..., arrn[i] contains atleast 1 true and false otherwise.
update 2
Some more examples as requested in the comments.
arrBefore = [[true],[false],[true],[false]];
arrAfter = [true];
---
arrBefore = [[true, false],[false, false], [false, true], [false, false], [true, true]];
arrAfter = [true, true];
---
arrBefore = [[true, false, false, false], [true, true, false, false]];
arrAfter = [true, true, false, false];
---
arrBefore = [[true, true, false, false, false], [false, false, false, false, true]];
arrAfter = [true, true, false, false, true];
Solution 1:[1]
You can use a Generator function to make this task easier.
You can give it an array that contains sub-arrays and then let it walk each of the sub arrays grabbing the item at the same index from each and then OR-ing them. Here is an overview of how this would work:
start:
iteration 1:
mainArray = [ [a01, a02, ..., aNN], [b01, b02, ..., bNN], [c01, c02, ..., cNN] ]
^^^ ^^^ ^^^
pointer | | |
OR together: a01 || b01 || c01 --> result01
iteration 2:
mainArray = [ [a01, a02, ..., aNN], [b01, b02, ..., bNN], [c01, c02, ..., cNN] ]
^^^ ^^^ ^^^
pointer | | |
OR together: a02 || b02 || c02 --> result02
...
iteration NN:
mainArray = [ [a01, a02, ..., aNN], [b01, b02, ..., bNN], [c01, c02, ..., cNN] ]
^^^ ^^^ ^^^
pointer | | |
OR together: aNN || bNN || cNN --> resultNN
end
You can then use Array.from to run through the entire algorithm and get an array from each iteration, so you'd get [result01, result02, ..., resultNN] from the above.
Here is an implementation:
function* sequentialOR(mainArray) {
//convert from arrays to iterators over the arrays
const iterators = mainArray.map(subArray => subArray[Symbol.iterator]());
//call .next() on each iterator to grab the values
let allResults = iterators.map(it => it.next());
//continue until the first is done.
//Since all sub-arrays have the same length this will be the same for all
while(allResults[0].done === false) {
yield allResults
.map(({value}) => value) //get the boolean value
.some(Boolean); //(essentially) run an OR operation
allResults = iterators.map(it => it.next());
}
}
/*
arrBefore = [[true],[false],[true],[false]];
arrAfter = [true];
*/
const input1 = [[true],[false],[true],[false]];
test(input1);
/*
arrBefore = [[true, false],[false, false], [false, true], [false, false], [true, true]];
arrAfter = [true, true];
*/
const input2 = [[true, false],[false, false], [false, true], [false, false], [true, true]];
test(input2);
/*
arrBefore = [[true, false, false, false], [true, true, false, false]];
arrAfter = [true, true, false, false];
*/
const input3 = [[true, false, false, false], [true, true, false, false]];
test(input3);
/*
arrBefore = [[true, true, false, false, false], [false, false, false, false, true]];
arrAfter = [true, true, false, false, true];
*/
const input4 = [[true, true, false, false, false], [false, false, false, false, true]];
test(input4);
//a quick function to print the output
function test(input){
const output = Array.from(sequentialOR(input));
console.log(
`Input: ${JSON.stringify(input)}
Output: ${JSON.stringify(output)}`
);
}
I've chosen to use Array#some here as it's a slightly more expressive way to implement OR. You can easily use Array#reduce to achieve the same effect but since reduce is more generic, it is slightly harder to understand that the operation is OR at a glance:
arrayOfBooleans.reduce((a, b) => a || b, false)
The initial value is set to false as this is the neutral/identity element when it comes to the OR operation. As you can see, while it might not be hard to see what's happening, it's not obvious at a glance, unlike .some. However, it's also a valid way to derive the boolean result.
This approach can be further generalised to produce any result with each of the elements if you just supply it with a callback to run against each set of results. So, we can re-write it to be more generic like this:
function sequentialOperation(operation) {
return function* (mainArray) {
const iterators = mainArray.map(subArray => subArray[Symbol.iterator]());
let allResults = iterators.map(it => it.next());
while(allResults[0].done === false) {
yield operation(
allResults.map(({value}) => value)
)
allResults = iterators.map(it => it.next());
}
}
}
const sequentialOR = sequentialOperation(arr => arr.some(Boolean));
/*
arrBefore = [[true],[false],[true],[false]];
arrAfter = [true];
*/
const input1 = [[true],[false],[true],[false]];
test(input1);
/*
arrBefore = [[true, false],[false, false], [false, true], [false, false], [true, true]];
arrAfter = [true, true];
*/
const input2 = [[true, false],[false, false], [false, true], [false, false], [true, true]];
test(input2);
/*
arrBefore = [[true, false, false, false], [true, true, false, false]];
arrAfter = [true, true, false, false];
*/
const input3 = [[true, false, false, false], [true, true, false, false]];
test(input3);
/*
arrBefore = [[true, true, false, false, false], [false, false, false, false, true]];
arrAfter = [true, true, false, false, true];
*/
const input4 = [[true, true, false, false, false], [false, false, false, false, true]];
test(input4);
//a quick function to print the output
function test(input){
const output = Array.from(sequentialOR(input));
console.log(
`Input: ${JSON.stringify(input)}
Output: ${JSON.stringify(output)}`
);
}
Thus we can also easily derive other operations, for example:
const sequentialAND = sequentialOperation(arr => arr.every(Boolean)); //or arr.reduce((a, b) => a && b, true)
const sequentialAdd = sequentialOperation(arr => arr.reduce((a, b) => a + b, 0));
const sequentialMax = sequentialOperation(arr => arr.reduce((a, b) => Math.max(a, b), -Infinity));
//and so on
Solution 2:[2]
How to reduce an array of booleans to a single boolean? Well, it depends upon binary operation you'd like to use. In your case, obviously, or is required, thus:
arrayOfBools.reduce((res, cur) => res || cur, false)
would work even if arrayOfBools is empty (false is default value returned in that way, which is logically correct).
How to reduce an array of arrays to a single array, i.e. how to flatten "higher order array" with reduce?
higherOrderArray.reduce((res, cur) => res.concat(cur), [])
That will do.
Now you can flatten your array of arrays of bools to a single flat array of bools and reduce it to a single bool. Note that classic loop will be much more efficient.
The other way is to flat your higher order array differently, using map. You could map your array of arrays of booleans to array of booleans using above mentioned or-drived reduce and reduce it one more time.
Functional implementation would be:
const mapReduce = <TSource, TTarget, TReduced>
(map: (x: TSource) => TTarget) =>
(reduce: (res: TReduced, cur: TTarget) => TReduced) =>
(zero: TReduced) =>
(arr: TSource[]) : TReduced => {
return !arr || !arr.length ? zero : arr.map(map).reduce(reduce, zero)
}
P.S. I wouldn't suggest to let such code leak into your prod unless your team is absolutely fine with functional programming. It just servers for learning purposes.
Solution 3:[3]
let arrBefore = [true, false, false],[false, false, false],[true, false, true],[true, false, true]]
let arrAfter = []
arrBefore.forEach((item) => {
arrAfter.push(!!item.find((i) => i === true))
})
console.log(arrAfter)
// [true, false, true, true]
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | marc_s |
| Solution 3 | Lua G |
