4

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];
5
  • Does this answer your question? Shortest way to "or" a list of booleans in javascript Commented Jan 13, 2020 at 9:39
  • Wait, you want to just OR the values of all equal indexes of the subarrays? Are you always going to have the same amount of items in each or can you have, say, [ [true, true], [true] ]? and if so, what is the expected output then? Oh, and do you absolutely need to use map and reduce? Because there might be more appropriate tools. Commented Jan 13, 2020 at 12:06
  • @VLAZ yes the returned array should always be the same length of the sub arrays. It is fine to use other tools then map() and reduce(). But want to avoid allot of nested loops. Commented Jan 13, 2020 at 12:11
  • Sorry, I meant what if the subarrays have a different length. Can that happen? Would it need to be handler and how? Commented Jan 13, 2020 at 12:14
  • @VLAZ No the sub arrays have the same length by definition. Commented Jan 13, 2020 at 12:18

3 Answers 3

4

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
Sign up to request clarification or add additional context in comments.

Comments

3

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.

Comments

0
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]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.