Creating underscores reduces a function from scratch

I am working on creating my own callback functions and higher order function groups. I am stuck in replicating underscore reduction functions or functions ._reduce. Can someone help me understand how this works under the hood, for me it has been a few days, and I'm at a standstill. Here is what I still have. Please understand that I do not use the underscore library, I am trying to reproduce it so that I can better understand higher-order functions. Thanks.

var reduce = function(collection, iterator, accumulator) { var iterator = function(startPoint, combiner){ for(var i = 0; i <combiner.length; i++){ startPoint += combiner[i]; } return iterator(accumulator, collection); } 
+7
javascript reduce
source share
3 answers

There was a lot of confusion in the comments on these answers between Underscore reduce and Array.prototype.reduce . Two notes:

  • The underline reduce allows an empty collection without an initial value. In this case, it does not throw an error, but returns undefined . Naomik convinced me that it was unsafe. For example, _([]).reduce(function(a, b) { return a + b}); should either throw an error or return an empty list.
  • The underline reduce works with both objects and arrays.

Now, to my original post:


I actually did the same thing - implemented key functions from Underscore from scratch - some time ago, and reduce was perhaps the most difficult. I think reduce easier to get with non-functional reduce (credit for naomik for this):

 function reduce(arr, func, seed) { var result = seed, len = arr.length, i = 0; for (; i < len; i++) { result = func(result, arr[i]) } return result } 

Implementing a subtree is a bit more complicated, handling objects and arrays, empty collections, and an optional initial value. It also uses each instead of a for loop, since it is more functional in style. This is my implementation of Underscore reduce :

 var reduce = function(coll, func, seed) { // `isEmpty` (not shown) handles empty arrays, strings, and objects. // Underscore accepts an optional seed value and does not // throw an error if given an empty collection and no seed. if (isEmpty(coll)) { return coll; } var noSeed = arguments.length < 3; // `each` (not shown) should treat arrays and objects // in the same way. each(coll, function(item, i) { if (noSeed) { // This condition passes at most once. If it passes, // this means the user did not provide a seed value. // Default to the first item in the list. noSeed = false; seed = item; } else { seed = func(seed, item, i); } }); return seed; }; 
-one
source share

A simple recursive function does the trick

 // arr - some array of values // f - the reducing function // acc - initial value for the accumulator function reduce(arr, f, acc) { if (arr.length === 0) return acc else return reduce(arr.slice(1), f, f(acc, arr[0])) } // -------------------------------------------------- // example 1: // reduce an array of numbers using an adding function var ex1 = reduce([1,2,3], function(acc, x) { return acc + x }, 0) console.log(ex1) //=> 6 // -------------------------------------------------- // example 2: // reduce an array of pairs to a mapping object var ex2 = reduce([['a', 1], ['b', 2], ['c', 3]], function(acc, pair) { var key = pair[0] var value = pair[1] acc[key] = value return acc }, {}) console.log(ex2) //=> { a: 1, b: 2, c: 3 } 

As @torazaburo notes in a comment, if you can use ES6, the destructuring assignment clears the implementation even more

 // ES6 function reduce([x, ...xs], f, acc) { if (x === undefined) return acc else return reduce(xs, f, f(acc, x)) } 

Or it becomes super sweet sweet with arrow functions

 // ES6, same as above but using arrow function and ternary expression const reduce = ([x, ...xs], f, acc)=> x === undefined ? acc : reduce(xs, f, f(acc, x)) 

The Underscore implementation does provide some other convenience, although I assume they are here to maintain compatibility with the native Array.prototype.reduce . I personally will not implement this, but it is not.

  • The underscore passes the iterator and arr reference to the callback function.
  • Underscore lets you change the context for a callback function

Here's a revised implementation that supports these other features.

 // our reduce version 2.0 function reduce(collection, iterator, memo, context) { function loop(memo, i) { if (collection.length === i) return memo else return loop(iterator.call(context, memo, collection[i], i, collection), i + 1) } return loop(memo, 0) } 

You can use it in the same way as above, only now it provides additional information for a callback

Note

I deliberately decided not to implement the Underscore pruning behavior, which allows pruning without an initial value. Supporting this behavior leads to unsafe code and should never have turned into Underscore.

+4
source share

This is something like this:

 function reduce(array, combine, start) { for (var i = 0; i < array.length; i++) start = combine(start, array[i]); return start; } console.log(reduce([1, 2, 3, 4], function(a, b) { return a + b; }, 0)); 

Link to link: http://eloquentjavascript.net/05_higher_order.html

0
source share

All Articles