-3

For a function that accepts an array of numbers and returns true if it contains at least one even number (false otherwise), I have the following 2 solutions.

Solution 1 (via forEach):

var hasEven = function (collection) {
    var isEven = false;
    collection.forEach(function (entry) {
        if (entry % 2 === 0) {
            isEven = true;
            return;
        }
    });
    return isEven;
}

Solution 2 (via reduce):

var hasEven = function (collection) {
    return collection.reduce(function (result, entry) {
        return (entry % 2 === 0) ? true : result; 
    }, false);
}

The first solution allows me to return with true, as soon as an even number is found. The second solution, on the other hand, provides the shortest code. Which one is better?

ps - Yes, there are better solutions... like using array.some(...). But I am particularly interested in comparing the solutions provided here. And yes, now the solutions are working properly. :)

Grateful
  • 9,685
  • 10
  • 45
  • 77
  • 4
    I don't think you tested that code. – Denys Séguret Feb 15 '16 at 07:41
  • Thank you for pointing that out. As you can tell... I am a newbie to this game and in the process of learning. – Grateful Feb 15 '16 at 08:11
  • 1
    Testing and generally putting some thought effort in a question before asking people to look at it has nothing to do with being a newbie. – Denys Séguret Feb 15 '16 at 08:31
  • 1
    I appreciate your comment Denys, but no one is born with complete knowledge on testing either. FYI, I did test my "solutions".. I just didn't do it properly. Hopefully, I will continue to learn from people like yourself and improve.... so yes, I am a newbie at developing in javascript which will obviously effect my level of testing in it as well. – Grateful Feb 15 '16 at 08:35
  • I have finally managed to understand, test and find the time to update the question above. – Grateful Feb 15 '16 at 10:30

1 Answers1

8

In your edited question, you've said:

I am particularly interested in comparing the solutions provided here

Is it better to use a chisel or a screwdriver to pound in a nail? The only correct answer is: Neither, use a hammer.

Comparing two solutions both using the wrong tool for the job ends up being a matter of opinion. Do you prefer simplicity? Go with the forEach. Brevity? Go with reduce. I would flag up either in a code review as being a suboptimal choice, and would view neither as more or less suboptimal than the other.


My original answer:

In this case, neither. Array#some (MDN | spec) would be the appropriate choice for determining whether an array has at least one entry that matches a criterion:

var hasEven = function(collection) {
    return collection.some(function(entry) {
        return entry % 2 === 0;
    });
};

Array#some stops looping as soon as the callback returns a truthy value. The return value of some is true if the callback returned a truthy value, or false if it didn't (all elements were tested and none matched).

Or with ES2015:

// ES2015 (aka ES6) only
let hasEven = function(collection) {
    return collection.some(entry => entry % 2 === 0);
};

Or we can get really condensed (possibly at the expense of readability, but maybe not when we're all really used to arrow functions):

// ES2015 (aka ES6) only
let hasEven = collection => collection.some(entry => entry % 2 === 0);

(This section references the code in your original question.)

I should note that neither of your example implementations works, neither the forEach nor the reduce one. The forEach one will always return false, because the return true inside the callback is just returning from the callback and doesn't have any effect at all. The reduce one will A) fail to test the first entry in the array; B) only tell you whether the last entry in the array is even, not if any of the previous ones were; and C) Return the value of the first entry if called on an array with only one entry, rather than either true or false.

A correct forEach would look like this (but will loop unnecessarily):

var hasEven = function(collection) {
    var result = false;
    collection.forEach(function(entry) {
        result = result || entry % 2 === 0;
    });
    return result;
};

A correct reduce version would look like this (but will loop unnecessarily):

var hasEven = function(collection) {
    return collection.reduce(function(result, current) {
        return result || current % 2 === 0;
    }, false);
};

Note that in the above, I used

return entry % 2 === 0;

rather than

 return (entry % 2 === 0) ? true : false;

The result of === is already a boolean.

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thank you for the wonderful answer. I am in the process of updating my question, thanks. – Grateful Feb 15 '16 at 08:14
  • 2
    @Grateful: No need to update the question. In fact, materially changing the question once it already has answers is a bit of a no-no on SO. – T.J. Crowder Feb 15 '16 at 08:18
  • I was actually already in the process of doing so, as per the first respondent @DenysSéguret. Sorry about that. – Grateful Feb 15 '16 at 08:19
  • 2
    @Grateful: He was just pointing out that those functions won't work. There's no need to update (and again, once answered, *materially* changing the question isn't appropriate). I've rolled back the edit modifying the `forEach` example (which did make it work, but had a pointless `return`). – T.J. Crowder Feb 15 '16 at 08:20
  • I appreciate your effort T. J. Crowder, but please realise that I began changing my question before your answer. It's just that as a newbie it took me time to make some of the modifications... and by then you had given out your full answer (still referring to some of the old parts). Denys had basically pointed out that my code didn't work and so I started testing it instantly. I appreciate the depth of your answer nonetheless. – Grateful Feb 15 '16 at 08:22
  • T.J. Crowder, before I select your answer (which I am about to do) would you be kind enough to explain a bit further as to why your reduce method works and mine doesn't... because as far as I can understand currently, you are simply return true or false at the end of every iteration as well... so why isn't it only checking for the last value like mine as well? – Grateful Feb 15 '16 at 08:32
  • 1
    @Grateful: Two reasons: First, I provide an initial value for the accumulator, which fixes items (A) and (C) on my list. Then the reason it fixes (B) is that once the accumulator is true, it will remain true for the rest of the iteration, because I'm doing `return result || entry % 2 === 0;` Without that `result ||` part, the accumulator's value is reset by each iteration, meaning you end up just with the flag for the last entry. – T.J. Crowder Feb 15 '16 at 08:36
  • @Grateful: `reduce` is a bit of an odd duck, it's not at all surprising for people to get it wrong (even people who aren't as new to JavaScript as you are). I'd suggest having a thorough read of the [MDN article](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) on it and playing with it a bit. It's not *complicated*, really, you'll get it without spending hours or anything like that, but it's an odd duck. :-) – T.J. Crowder Feb 15 '16 at 08:39
  • Excellent. Thank you so much. I think the penny has finally dropped... and you have been the primary source of that :) Just for further comprehension, would you kindly find the time (if possible) to explain which of the solutions specifically mentioned in the question are better. Thanks. – Grateful Feb 15 '16 at 10:35
  • @Grateful: I've updated, but the answer really is the same: *Neither* is better. If there were no `Array#some` method, I'd be saying "use a `for` loop." `forEach` and `reduce` are both just the wrong tool for the job. – T.J. Crowder Feb 15 '16 at 10:46