3

I need to write a small map/reduce function that should return 1 if there are at least 4 continuous 0 in an array, otherwise it should return something different than 1.

Example:

[6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1] => 1

[6, 7, 5, 0, 0, 0, 3, 4,0, 0, 0, 0, 0, 1] => 1

[5, 0, 0, 0, 3, 4, 0, 0, 8, 0, 0, 1] => something # 1

This is my algorithm:

[6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]
    .map((i) => i === 0 ? 1 : 0)
    .reduce((prev, i) => {
      if (i !== 0) {
         return prev + i;
      } else {
        if (prev >= 4) {
          return 4;
        }
        return 0;
      }
    }, 0);

The .map method will mark 0 as 1 and non-0 as zero. So [6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1] becomes [0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0]. Then the .reduce method will collect the number of zeroes.

If the current element is 1 (meaning zero in the input array) it returns the previous value plus the current value (1). This means it represents the number of zeroes. If the current item is 0 (meaning not 0 in the input array), it resets prev to 0 when it is less than 4, otherwise it carries on.

At the end, if the value is 4, it means that there are at least 4 continuous 0.

The algorithm seems to run, but not meet the requirement. It requires returning 1 if the input has 4 consecutive 0, otherwise it should return any non-zero number.

Do you think of any way to do this?

Community
  • 1
  • 1
James
  • 13,571
  • 6
  • 61
  • 83
  • 2
    If you don't need to sum the array wouldn't it be easier to do `arr.toString().indexOf('0000') > -1` – Daniel Lizik Aug 26 '16 at 18:49
  • The point is I need map -reduce algorithm. This can be done using more creative way like below answer. – James Aug 26 '16 at 21:58

4 Answers4

3

You could use Array#some and a this object for counting.

Array#some allows to end the iteration if the right count is found, as opposite of Array#reduce, which iterates all elements.

function isContinuous(array) {
    return +array.some(function (a, i) {
        if (a === 0) {
            this.count++;
            return this.count === 4;
        }
        this.count = 0;
    }, { count: 0 });
}

console.log(isContinuous([6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]));
console.log(isContinuous([6, 7, 5, 0, 0, 0, 3, 4, 0, 0, 0, 0, 0, 1]));
console.log(isContinuous([5, 0, 0, 0, 3, 4, 0, 0, 8, 0, 0, 1]));
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • This is cool, what does the `+` in front of the `array` mean? – Muntasir Alam Aug 26 '16 at 20:01
  • `some` returns `true` or `false`, but we need a numerical value, like `1` for `true` and `0` (a value different from `1`) for `false`. the plus operator is unary and cast the following value to `Number`. – Nina Scholz Aug 26 '16 at 20:05
3

Here's an ES6 function that uses a 4-bit representation, where each 1-bit represents a non-zero value in the input array.

Only the most recent 4 bits are retained: each iteration the next bit is shifted in from the right, while the left most is dropped, always keeping 4 bits. If the outcome becomes zero at any time, the loop stops:

function has0000(arr) {
    let c = 1;
    return +!arr.every( n => c = (c*2+!!n) & 0xF );
}

console.log(has0000([6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]));
console.log(has0000([6, 7, 5, 0, 0, 0, 3, 4, 0, 0, 1]));

Your code

Although the algorithm for counting zeroes is correct, the return value is not a 1 when needed, and vice versa.

The return value is whatever is the last returned value in the reduce callback, so it represents a count, which could be any non-negative number.

Even when there are 4 consecutive zeroes found, this count can start from zero again, and so you lose the knowledge that you actually had a match.

The main fix to this is to swap the if statements, and do the if (prev >= 4) first. That way you will keep the count at 4 once it reaches that, no matter what follows. It will never get reset to 0 anymore.

The second fix is to detect the final return is exactly 4 and transform that to a one. This must be done in a way that 4 is the only value that gets transformed to a 1, nothing else. This you could do by dividing by 4.

So without changing anything else, with minimal changes, this is the corrected code:

var res = [6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]
    .map((i) => i === 0 ? 1 : 0)
    .reduce((prev, i) => {
      if (prev >= 4) {
         return 4;
      } else if (i !== 0) {
         return prev + i;
      } else {        
        return 0;
      }
    }, 0) / 4;
    
console.log(res);

The downside of sticking to only map and reduce is that you cannot exit before having gone through all input elements. This is a pity, because once you have found 4 zeroes, there really is no reason to keep going. The result is already final at that moment.

And here is your corrected code in a more condensed format, where I also left out the map as it is a trivial thing to incorporate in the reduce callback:

var res = [6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]
    .reduce((prev, i) => prev >= 4 ? 4 : i ? 0 : prev + 1, 0) 
    / 4;
    
console.log(res);
trincot
  • 317,000
  • 35
  • 244
  • 286
1

Using .map and .reduce to meet your requirement:

//the following returns 1 if there is at least 4 consecutive 0's
//otherwise returns 0, 2, 4 or 6:

function cons4zeroes(arr) {
  return arr
    .map((i) => i ^ 0 ? 0 : 2)
    .reduce((p, i) => p ^ 1 ?
      i ^ 0 ?
        (p + i) % 7 :
        0 :
      1,
      0)
}

console.log(
  cons4zeroes([6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]), " => 1"
);
console.log(
  cons4zeroes([6, 7, 5, 0, 0, 0, 3, 4, 0, 0, 0, 0, 0, 1]), " => 1"
);
console.log(
  cons4zeroes([5, 0, 0, 0, 3, 4, 0, 0, 8, 0, 0, 1]), " => something # 1"
);

This solution builds on your logic, but maps each 0 to 2 instead of 1, reserving 1 for when 4 consecutive 0's have been found. Then, summing the consecutive 2's, resetting to 0 if a non-zero value is found before the 4th zero, taking the sum modulo 7 to convert the sum to 1 when 4 zeroes are found (4 × 2 = 1 mod 7).

Alternative without .map():

//the following returns 1 if there is at least 4 consecutive 0's
//otherwise returns 0, 2, 4 or 6:

function cons4zeroes(arr) {
  return arr.reduce((p, i) => p ^ 1 ?
      i ^ 0 ? 0 : (p + 2) % 7 : 1, 0)
}

console.log(
  cons4zeroes([6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]), " => 1"
);
console.log(
  cons4zeroes([6, 7, 5, 0, 0, 0, 3, 4, 0, 0, 0, 0, 0, 1]), " => 1"
);
console.log(
  cons4zeroes([5, 0, 0, 0, 3, 4, 0, 0, 8, 0, 0, 1]), " => something # 1"
);
Tomas Langkaas
  • 4,551
  • 2
  • 19
  • 34
1

In general, your implementation looks good to me, except the small nuance: you should specify -3 as an initial value for the reduce function (and modify its body accordingly in order to satisfy your requirements).

Below provided the modified version of your implementation, which returns 1 in case if there are present at least 4 consecutive zeroes, and otherwise returns one of the following values: -3, -2, -1 or 0

var result = [6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]
    .map((i) => i === 0 ? 1 : 0)
    .reduce((prev, i) => {
      if (prev === 1) {
          return 1;
      }
      if(i === 0) {
          return -3;
      }
      return prev + (i === 1);
    }, -3);

console.log(result)
stemm
  • 5,960
  • 2
  • 34
  • 64