2

In the process of learning functional programming, I am trying to refactor the following code using map, filter, and/or reduce.

I see that I can deal with the conditional using the filter method, but don't know how to handle the repeated assignment so I can avoid use of a for loop.

I am thinking that I would use the map method to handle the diff assignments, and chain a filter method that would deal with the conditional. Am I on the right track?

Would someone be kind enough to refactor the following code in the functional paradigm and explain. Thanks.

This function finds the first non-consecutive number in an array.

 function firstNonConsecutive (arr) {
   var diff = 0;
    for(var i = 0; i < arr.length; i++) {
        diff = arr[i+1] - arr[i];
        if(diff > 1) {
            return arr[i+1];
        }
    }
    return null;
efw
  • 449
  • 3
  • 16
  • The repeated assignment is pretty pointless. The code works the same as if `diff` was declared with `const` inside the loop body. – Bergi Jun 06 '20 at 19:45

4 Answers4

1

Consider using Array.find https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find, like most functional array functions it takes a callback/predicate that takes 3 parameters, the item, the index and the whole array. With this you can look ahead/behind in the same way you currently do. Like this:

 function firstNonConsecutive2(arr) {
   return arr.find((item, index, array) => {
     const diff = array[index - 1] - item; // Use index -1 to look behind instead of ahead since we want to return/find the item that is non-consecutive
     return diff === 0; // I think this was a small bug in your version which worked if the numbers were incrementing only
   });
 }

In the first iteration of the find "loop" it'll try to diff undefined with for example 1, which is NaN, NaN is not equal to 0 so it keeps searching. Next it'll try maybe 1 and 2 so diff becomes -1 so it keeps searching. Until it reaches for example 5 and 5, which diffs to 0 so the find predicate is now true, so it will return the second 5 since that is the current item, we're looking behind us by using index - 1.

Let me know if you want further explanation of something!

Milton
  • 970
  • 4
  • 13
  • Thanks, Milton. This is helpful. The algorithm I wrote was to a challenge that said all the numbers were unique and in ascending order, which may account for what is seen as a bug. Functional programming is new to me, so I'll have to take a closer look at what you wrote. Thanks again for taking the time and effort to help. – efw Jun 05 '20 at 20:18
0

If you are looking into fp, then also recursion would find a good application here:

const firstNonConsecutive = (list) => {
  if (!list.length) { 
    // list is empty, not found
    return -1;
  }
  
  const [head, ...tail] = list;
  const [n] = tail;
  
  if (n - 1 !== head) { 
    // found
    return n;
  }
  
  // yet another round
  return firstNonConsecutive(tail);
};

console.log(
  firstNonConsecutive([1, 2, 3, 4, 5, 6, 7, 9, 10]),
);
Hitmands
  • 13,491
  • 4
  • 34
  • 69
0

You want to use Array.find, not .map, .filter, or .reduce. You could find a way to use those, but they waste time because they don't return as soon as the first match is found.

Here are some progressively more verbose solutions to help you understand how the first one works.

The second is the most functional, because it's declarative unlike the first.

array.find(nonConsecutive) reads close to plain English and declares what you want to do, leaving the imperative implementation details hidden away inside the nonConsecutive function.

const array = [1, 2, 3, 4, 5, 6, 7, 9, 10];

console.log(
  array.find((n, i) => i && n != array[i - 1] + 1) // 9
);

const nonConsecutive = (n, i, arr) => i && n != arr[i - 1] + 1;
console.log(
  array.find(nonConsecutive) // 9
);

console.log(
  array.find(function (number, index) { // we don't need third "array" argument because the array is already in scope.
    if (index == 0) return false; // if index is zero, return. Otherwise, next line would access index -1.
    if (number != array[index - 1] + 1) return true; // if number is not equal to the the previous number, plus one, it's not consecutive. Return it.
    return false; // if we reach this line than the number must be consecutive, so return false.
  }) // 9
);
GirkovArpa
  • 4,427
  • 4
  • 14
  • 43
0

Recursion by mathematical induction -

  1. If the first element, a, or the second element, b, are null, return undefined
  2. (induction) Neither the first element, a, nor the second element, b are null. If b is consecutive to a, return the recursive result of the smaller problem
  3. (induction) Neither the first element, a, nor the second element, b, are null and b is not consecutive to a. Return the answer, b.

const firstNonConsecutive = ([ a, b, ...more ]) =>
  a == null || b == null
    ? undefined                            // 1
: b === a + 1
    ? firstNonConsecutive([ b, ...more ])  // 2
: b                                        // 3

console.log(firstNonConsecutive([ 4, 5, 6, 8, 9, 10 ]))
// 8

console.log(firstNonConsecutive([ 7, 8, 9, 10, 13, 14 ]))
// 13

console.log(firstNonConsecutive([ 99 ]))
// undefined

console.log(firstNonConsecutive([]))
// undefined
Mulan
  • 129,518
  • 31
  • 228
  • 259