5

I guess this is two questions. I am still having trouble with the reduce method, I get the simple way of using it

reduce([1,2,3], function(a, b) { return a + b; }, 0); //6

Using it with anything other than numbers really confuses me. So how would I build a contains function using reduce in place of the for loop? Comments would be appreciated. Thank you all.

function contains(collection, target) {
  for(var i=0; i < collection.length; i++){
    if(collection[i] === target){
      return true;
    }
  }
  return false;
}
contains([1, 2, 3, 4, 5], 4);
//true
Jad Chahine
  • 6,849
  • 8
  • 37
  • 59
hackermann
  • 119
  • 2
  • 6
  • I don't think you can do it with `Array.prototype.reduce` but you could write your own `reduce` function that understands `reduced` values (that terminate the iteration). – MinusFour Oct 20 '15 at 18:02
  • 2
    Uhm, `Array.indexOf` ? – adeneo Oct 20 '15 at 18:02
  • Why would you use reduce for contains? – epascarello Oct 20 '15 at 18:02
  • actually reduce() makes some sense because you want to transform many into one. – dandavis Oct 20 '15 at 18:03
  • example seems to trivial since `indexOf()` is simplest and already exists – charlietfl Oct 20 '15 at 18:05
  • I know you can use `Array.indexOf` but I need to build contains using reduce. This question was on an interview question so that is the reason why. – hackermann Oct 20 '15 at 18:10
  • 1
    In a few years you can use [**Array.includes**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes), note that there is a polyfill on MDN. – adeneo Oct 20 '15 at 18:10
  • i fear nobody will use includes because it's not named contains like the string version. why does the string version always come out first (like indexOf)? anyway, thank you moo tools for the dumb name... – dandavis Oct 20 '15 at 18:16
  • Impress the interview panel by informing them that using `some` would be more efficient because it exits the loop as soon as the contained item is found. – Jeremy Larter Oct 20 '15 at 21:55

3 Answers3

2

This is what you need:

function contains(collection, target) {
    return collection.reduce( function(acc, elem) {
       return acc || elem == target;
    }, false)
};

As adaneo says, there is probably an easier way for this particular question, but you tagged this 'functional programming' so I guess you want to get better at this way of tackling problems, which I totally endorse.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Simon H
  • 20,332
  • 14
  • 71
  • 128
  • you could bind() target to this to kill the wrapper/closure, don't forget to add "use strict" in that case. – dandavis Oct 20 '15 at 18:10
  • 1
    The easier way would be `collection.some(function(elem) { return elem == target; })`, wherein we even could implement `some` using `reduce`. – Bergi Oct 20 '15 at 18:47
  • @FreddieCabrera great, could you mark the question completed then – Simon H Oct 20 '15 at 19:23
  • @SimonH how does this exactly work? Would you walk me through it? – hackermann Oct 21 '15 at 19:24
  • 1
    Sure. Reduce takes a `acc`umulator with a start value - false in this case - and combines that with each `elem`ent in the list. The function that combines the two returns a new value of `acc` for the next iteration. At the end the final value of the accumulator is returned to `contains` – Simon H Oct 21 '15 at 19:42
  • 1
    I guess you see that the Boolean logic starts as false and stays that way until `elem == target` at which point the `acc` becomes true and stays that way – Simon H Oct 21 '15 at 20:06
2

Here is a ES2015 solution:

    const contains = (x, xs) => xs.some(y => x === y);
    let collection = [1,2,3,4,5];

    console.log(contains(4, collection)); // true;

The big advantage of Array.prototype.some compared to Array.prototype.reduce is that the former exits the iteration as soon as the condition is true, whereas the latter always traverses the whole array. That means contains(4, xs) stops it iteration with the fourth element of xs.

  • Well, OP asks specifically for a `reduce` solution... apart from that, you are right of course – le_m Jul 13 '16 at 20:14
  • 1
    @le_m You're right. But sometimes we need to respond differently than the OP expects - even if this means that we don't gain reputation :D –  Jul 13 '16 at 20:21
  • @LUH3417 I agree 100% – Mulan Jul 15 '16 at 00:01
1

How do I do X using Y ?

Generally I approach all of these questions the same way: programming languages aren't meant to be magic wands. If your language doesn't come with a built-in feature or behaviour, you should be able to write it on your own. From there, if you later learn that your language does offer a built-in for such a behaviour (or it is added), then you can refactor your code if you wish. But by all means, do not sit around waiting for the magic wand to be waved and for your code to magically work.

You can use Array.prototype.reduce if you want, but given the way it works, it will always iterate through the entire contents of the array — even if a match is found in the first element. So this means you shouldn't use Array.prototype.reduce for your function.

However, you can use a reduce solution if you write a reduce which supports an early exit. Below is reducek which passes a continuation to the callback. Applying the continuation will continuing reducing, but returning a value will perform an early exit. Sounds like exactly what the doctor ordered...

This answer is meant to accompany LUH3417's answer to show you that before you might be aware of Array.prototype.some, you shouldn't sit around waiting for ECMAScript to implement behaviour that you need. This answer demonstrates you can use a reducing procedure and still have early exit behaviour.

const reducek = f=> y=> ([x,...xs])=>
  x === undefined ? y : f (y) (x) (y=> reducek (f) (y) (xs))

const contains = x=>
  reducek (b=> y=> k=> y === x ? true : k(b)) (false)

console.log(contains (4) ([1,2,3,4,5])) // true
console.log(contains (4) ([1,2,3,5]))   // false
console.log(contains (4) ([]))          // false

Seeing reducek here and the example contains function, it should be somewhat obvious that contains could be generalized, which is exactly what Array.prototype.some is.

Again, programming isn't magic, so I'll show you how you could do this if Array.prototype.some didn't already exist.

const reducek = f=> y=> ([x,...xs])=>
  x === undefined ? y : f (y) (x) (y=> reducek (f) (y) (xs))

const some = f=>
  reducek (b=> x=> k=> f(x) ? true : k(b)) (false)

const contains = x=> some (y=> y === x)

console.log(contains (4) ([1,2,3,4,5])) // true
console.log(contains (4) ([1,2,3,5]))   // false
console.log(contains (4) ([]))          // false
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • I don't think you should pass `y` to `f`, and the continuation shouldn't take a parameter – Bergi Jul 15 '16 at 01:43
  • @Bergi, I'm intrigued ^_^ Can you link a code paste that works with the recommendations you've given ? – Mulan Jul 15 '16 at 05:42
  • Just `const reducek = f => y => xs => xs.length == 0 ? y : f(xs[0])(() => reducek(f)(y)(xs.slice(1))` and `const contains = x => reducek (y => k => y === x || k())(false)` – Bergi Jul 15 '16 at 12:06
  • @Bergi OK, now `reducek` can't be applied to normal lambdas anymore, but only to lambdas that can deal with laziness. But we can derive more specific higher order functions like `map`, `some` or `contains`, which all "inherit" `reducek`'s early exit behavior. Or did I miss something? –  Jul 15 '16 at 12:40
  • @LUH3417: I have no idea what you mean by "normal lambdas", but yes, you can derive pretty much any list function from `reducek` – Bergi Jul 15 '16 at 12:43
  • @Bergi Oh, sorry, here is an example: `reducek(x => y => x + y)([])[1,2,3])` doesn't work anymore. It requires `x => y => x + y()`. –  Jul 15 '16 at 12:46
  • 1
    @LUH3417: Yes, dealing with lazyness explicitly is necessary for early exiting, but notice that your example didn't work with naomik's original `reducek` either – Bergi Jul 15 '16 at 12:48
  • @Bergi Noticed. Silly me. –  Jul 15 '16 at 12:51
  • 1
    I was intending for `reducek` to be used specifically when you wanted to control laziness. LUH3417's sum example could use `reducek (y=> x=> k=> k (x + y)) (0) ([1,2,3]) //=> 6` but it wouldn't make sense to use `reducek` in this scenario. Instead, the non-lazy reduce, `reduce` would be more fitting. `const reduce = f=> reducek (y=> x=> k=> k (f (y) (x)))` where `reduce (y=> x=> x + y) (0) ([1,2,3]) //=> 6` would work without any ability to exit early. – Mulan Jul 15 '16 at 16:14
  • My intention was merely to defend your version of `reducek`, but I failed :( –  Jul 15 '16 at 16:21
  • @LUH3417 it's okay. I'm still having trouble seeing how I could get something like `stopAddingAfter20` to work with Bergi's simplified interface. With my code, I could do `const stopAddingAfter20 = reducek (acc=> x=> k=> acc > 20 ? acc : k(acc + x)) (0)` then `stopAddingAfter20 ([1,2,3,4,5,6,7,8,9]) //=> 21` but I can't figure out how to get similarly functioning code without introducing kludgy free variables. The reducer lambda has traditionally received two parameters, `acc` and `x`, so replacing one of those with the continuation interface seems like you take away some of its utility. – Mulan Jul 15 '16 at 16:43
  • @Bergi, to continue, I like the simplicity of your `reducek` but I don't see how it can be applied in other general cases :( – Mulan Jul 15 '16 at 16:48
  • It seems to be difficult to implement [`take`-like functions](http://stackoverflow.com/q/15879940/6445533) with this `reducek` version (aka `foldr`). Maybe it works with an left associative `fold`. I'll try to figure that out. –  Jul 15 '16 at 17:38
  • @LUH3417 this is specifically a left-folding `reducek` (probably better named as `foldlk`. A right-folding reduce would need to be written separately. Here's `foldr`: `const foldr = f=> y=> ([x,...xs])=> x === undefined ? y : f (foldr (f) (y) (xs)) (x)` and an example use `foldr (y=> x=> `(${y} + ${x})`) (0) ([1,2,3]) //=> '(((0 + 3) + 2) + 1)'`. I'm not instantly sure on how to convert that to `foldrk` (or if it's possible) but I will come back to it later today ^_^ – Mulan Jul 15 '16 at 17:55
  • @LUH3417 I misread your last comment. `reducek` is definitely *not* `foldr`. Implementing `take` with `reducek` is trivial. `const take = n=> reducek (xs=> x=> k=> xs.length === n ? xs : k([...xs, x])) ([])` and `take (3) ([1,2,3,4,5]) //=> [1,2,3]` – Mulan Jul 15 '16 at 18:50
  • Hmm, either your `foldr` is weird or I am confused (the latter probably). When I apply this `foldr` to `concat = ys => xs => xs.concat(ys)` it doesn't work. You just flipped the arguments containing the recursive call and `x` in your implementation. To change the associativity you need to change the order of `foldx` and `f` in my view: `foldl = f => y => ([x, ...xs]) => xs.length ? foldl(f)(f(y)(x))(xs) : f(y)(x)` and `foldr = f => y => ([x, ...xs]) => xs.length ? f(x)(foldr(f)(y)(xs)) : f(x)(y)`. Again, this might also be wrong. I'll check it again and maybe provide it as a question on SO. –  Jul 15 '16 at 20:37
  • So this portion in your original `reducek` function `f(y)(x)(y => reducek...` compared to `f(x)(() => reducek...` of Bergi's version makes the difference and needs further examination. –  Jul 15 '16 at 20:51
  • *"You flipped the arguments containing the recursive call and `x`..."* no, it's not quite that simple. If you compare `foldr` to something, it must be to an equivalent `foldl` that doesn't use an underlying `foldlk` implementation. The difference is very apparent in [this gist](https://gist.github.com/naomik/01aaab65f4b469b59f22cf51d8f251c6). – Mulan Jul 15 '16 at 20:59
  • @LUH3417 *"Bergi's version makes the difference ..."* yes I understand the difference here but I'm still waiting for him to chime in on my other question about it. I'm not sure how to implement non-trivial procedures (like `stopAddingAfter20` I referenced above) using his code. – Mulan Jul 15 '16 at 21:07
  • OK, we are on the same page regarding `foldl`/`foldr`. I think that Bergi's version of your `reducek` is just a lazy `foldr` and `foldr` doesn't support `stopAddingAfter20` in a pure functional way because it is right associative and thus starts the actual computation (of `f`) from right to left: `...(6+(7+(8+9)))`. –  Jul 15 '16 at 21:16
  • @LUH3417, yeah I had some time to look at this closer. Bergi's `reducek` is in-fact a right-folding reduce which is why it behaves differently. I'm pretty sure it's not possible to get early exit behaviour from a right fold because the computation cannot collapse until the right-most list element is evaluated. An obvious cheat would be to reverse the input list and then left-fold it (but wouldn't work on infinite lists/generators, of course) – Mulan Jul 15 '16 at 23:52
  • @naomik: Ah, I see, your `reducek` did allow to left-fold as well. Mine only allows laziness in the second argument of the reducer. – Bergi Jul 16 '16 at 08:41
  • @naomik You can simplify your `some`: `const some = pred => reducek(() => x => k => pred(x) || k(false))();`. This implementation also reveals the superfluity of the accumulator in connection with `some`/`every`(see my comments below). –  Jul 16 '16 at 11:54
  • @naomik Why can `some`/`every` be derived from @Bergi's `reducek` but not your `stopAddingAfter20`? The problem is that `reducek`'s accumulator is "overloaded" so to speak. While the stack is build up, it is a thunk. When the stack is unwound it is an arbitrary value. Logically the accumulator can't be used for early exiting. So we can only derive list functions from `reducek` that don't rely on the accumulator. But `some`/`every` evidently compare two arguments and thus expect a binary reducer? ... –  Jul 16 '16 at 14:15
  • @naomik ... However, the first argument of `some`/`every`'s callback is provided as a free variable outside of the recursive iteration. Your `reducek` introduces the continuation as another argument, which exempt the accumulator from beeing a thunk. Now we can derive list functions that rely on the accumulator as well. Your `reducek` acts more like a function in continuation passing style than a lazy evaluated one. –  Jul 16 '16 at 14:16
  • @LUH3417 true that `some` can be written that way. `some` and `every` are sort of an exceptional cases because at each iteration, we can throwaway any previous answer, thus no accumulator is needed. This is possible because we can logically deduce that: if the current iteration is running, we still don't have an early-exited answer from a previous iteration, which is the only thing the accumulator would be able to tell us anyway ... – Mulan Jul 16 '16 at 16:11
  • @LUH3417 ... `some` and `every` can be folded left *or* right and the answer will always be the same. This isn't always the case tho. My intention was to provide a more general-purpose `reducek` that would be suitable for any function that requires an early exit. – Mulan Jul 16 '16 at 16:11