I used both methods but I am quite confused regarding the usage of both methods.
Is anything that map can do but reduce can not and vice versa?
Note: I know how to use both methods I am questioning for main difference between these method and when we need to used.
Source
Both map and reduce have as input the array and a function you define. They are in some way complementary: map cannot return one single element for an array of multiple elements, while reduce will always return the accumulator you eventually changed.
map
Using map you iterate the elements, and for each element you return an element you want.
For example, if you have an array of numbers and want to get their squares, you can do this:
// A function which calculates the square
const square = x => x * x
// Use `map` to get the square of each number
console.log([1, 2, 3, 4, 5].map(square))
reduce
Using an array as an input, you can get one single element (let's say an Object, or a Number, or another Array) based on the callback function (the first argument) which gets the accumulator and current_element parameters:
const numbers = [1, 2, 3, 4, 5]
// Calculate the sum
console.log(numbers.reduce(function (acc, current) {
return acc + current
}, 0)) // < Start with 0
// Calculate the product
console.log(numbers.reduce(function (acc, current) {
return acc * current
}, 1)) // < Start with 1
Which one should you choose when you can do the same thing with both? Try to imagine how the code looks. For the example provided, you can compute the squares array like you mentioned, using reduce:
// Using reduce
[1, 2, 3, 4, 5].reduce(function (acc, current) {
acc.push(current*current);
return acc;
}, [])
// Using map
[1, 2, 3, 4, 5].map(x => x * x)
Now, looking at these, obviously the second implementation looks better and it's shorter. Usually you'd choose the cleaner solution, which in this case is map. Of course, you can do it with reduce, but in a nutshell, think which would be shorter and eventually that would be better.
I think this picture will answer you about the difference between those Higher Order Functions
Generally "map" means converting a series of inputs to an equal length series of outputs while "reduce" means converting a series of inputs into a smaller number of outputs.
What people mean by "map-reduce" is usually construed to mean "transform, possibly in parallel, combine serially".
When you "map", you're writing a function that transforms x with f(x) into some new value x1. When you "reduce" you're writing some function g(y) that takes array y and emits array y1.
They produce different results in terms of data structure.
The map() function returns a new array through passing a function over each element in the input array.
This is different to reduce() which takes an array and a function in the same way, but the function takes 2 inputs - an accumulator and a current value.
So reduce() could be used like map() if you always .concat onto the accumulator the next output from a function. However it is more commonly used to reduce the dimensions of an array so either taking a one dimensional and returning a single value or flattening a two dimensional array etc.
Let's take a look of these two one by one.
Map
Map takes a callback and run it against every element on the array but what's
makes it unique is it generate a new array based on your existing array.
var arr = [1, 2, 3];
var mapped = arr.map(function(elem) {
return elem * 10;
})
console.log(mapped); // it genrate new array
Reduce
Reduce method of the array object is used to reduce the array to one single value.
var arr = [1, 2, 3];
var sum = arr.reduce(function(sum, elem){
return sum + elem;
})
console.log(sum) // reduce the array to one single value
I think this question is a very good question and I can't disagree with the answers but I have the feeling we are missing the point entirely.
Thinking of map and reduce more abstractly can provide us with a LOT of very good insights.
This answer is divided in 3 parts:
Defining and deciding between map and reduce (7 minutes)
Using reduce intentionally (8 minutes)
Bridging map and reduce with transducers (5 minutes)
map or reduce
Common traits
map and reduce are implemented in a meaningful and consistent way on a wide range of objects which are not necessarily collections.
They return a value useful to the surrounding algorithm, and they only care about this value.
Their major role is conveying intent regarding transformation or preservation of structure.
Structure
By "structure" I mean a set of conceptual properties which characterise abstract objects, such as an unordered list or a 2D matrix, and their concretion in data structures.
Note that there can be a disconnect between the two:
an unordered list can be stored as an array, which has the concept of ordering carried by indexed keys;
a 2D matrix can be stored as a TypedArray, which lacks the concept of dimension (or nesting).
map
map is a strict structure-preserving transformation.
It is useful to implement it on other kinds of objects to grasp its semantic value:
class A {
constructor (value) {
this.value = value
}
map (f) {
return new A(f(this.value))
}
}
new A(5).map(x => x * 2); // A { value: 10 }
Objects implementing map can have all kinds of behaviours, but they always return the same kind of object you started with while transforming the values with the supplied callback.
Array.map returns an array of the same length and the same ordering as the original.
On the callback arity
Because it preserves structure, map is viewed as a safe operation, but not every callback is equal.
With a unary callback: map(x => f(x)), each value of the array is totally indifferent to the presence of other values.
Using the other two parameters on the other hand introduces coupling, which may not be true to the original structure.
Imagine removing or reordering the second item in the arrays bellow: doing it before or after the map would not yield the same result.
Coupling with array size:
[6, 3, 12].map((x, _, a) => x/a.length);
// [2, 1, 4]
Coupling with ordering:
['foo', 'bar', 'baz'].map((x, i) => [i, x]);
// [[0, 'foo'], [1, 'bar'], [2, 'baz']]
Coupling with one specific value:
[1, 5, 3].map((x, _, a) => x/Math.max(...a));
//[ 0.2, 1, 0.6]
Coupling with neighbours:
const smooth = (x, i, a) => {
const prev = a[i - 1] ?? x;
const next = a[i + 1] ?? x;
const average = (prev + x + next) / 3;
return Math.round((x + average) / 2);
};
[1, 10, 50, 35, 40, 1].map(smoothh);
// [ 3, 15, 41, 38, 33, 8 ]
I recommend making it explicit on the call site whether or not these parameters are used.
const transfrom = (x, i) => x * i;
❌ array.map(transfrom);
⭕ array.map((x, i) => transfrom(x, i));
This has other benefits when you use variadic functions with map.
❌ ["1", "2", "3"].map(parseInt);
// [1, NaN, NaN]
⭕ ["1", "2", "3"].map(x => parseInt(x));
// [1, 2, 3]
reduce
reduce sets a value free from its surrounding structure.
Again, let's implement it on a simpler object:
class A {
constructor (value) {
this.value = value
}
reduce (f, init) {
return init !== undefined
? f(init, this.value)
: this.value
}
}
new A(5).reduce(); // 5
const concat = (a, b) => a.concat(b);
new A(5).reduce(concat, []); // [ 5 ]
Whether you leave the value alone or you put it back into something else, the output of reduce can be of any shape. It is literally the opposite of map.
Implications for arrays
Arrays can contain multiple or zero values, which gives rise to two, sometimes conflicting, requirements.
The need to combine
How can we return multiple values with no structure around them?
It is impossible. In order to return only one value, we have two options:
summarising the values into one value;
moving the values into a different structure.
Doesn't it make more sense now?
The need to initialise
What if there is no value to return?
If reduce returned a falsy value, there would be no way to know if the source array was empty or if it contained that falsy value, so unless we provide an initial value, reduce has to throw.
The true purpose of the reducer
You should be able to guess what the reducer f does in the following snippet:
[a].reduce(f);
[].reduce(f, a);
Nothing. It is not called.
It is the trivial case: a is the single value we want to return, so f is not needed.
This is by the way the reason why we didn't make the reducer mandatory in our class A earlier: because it contained only one value. It is mandatory on arrays because arrays can contain multiple values.
Since the reducer is only called when you have 2 or more values, saying its sole purpose is to combine them is only a stone throw away.
On transforming values
On arrays of variable lengths, expecting the reducer to transform the values is dangerous because, as we discovered, it may not be called.
I encourage you to map before you reduce when you need to both transform values and change shape.
It is a good idea to keep these two concerns separate for readability anyway.
When not to use reduce
Because reduce is this general-purpose tools for achieving structure transformation, I advise you to avoid it when you want an array back if there exists another more focussed method which does what you want.
Specifically, if you struggle with nested arrays in a map, think of flatMap or flat before reaching for reduce.
At the heart of reduce
a recursive binary operation
Implementing reduce on arrays introduces this feedback loop where the reducer's first argument is the return value of the previous iteration.
Needless to say it looks nothing like map's callback.
We could implement Array.reduce recursively like so:
const reduce = (f, acc, [current, ...rest]) =>
rest.length == 0
? f(acc, current)
: reduce(f, f(acc, current), rest)
This highlights the binary nature of the reducer f and how its return value becomes the new acc in the next iteration.
I let you convince yourself that the following is true:
reduce(f, a, [b, c, d])
// is equivalent to
f(f(f(a, b), c), d)
// or if you squint a little
((a ❋ b) ❋ c) ❋ d
This should seem familiar: you know arithmetic operations obey rules such as "associativity" or "commutativity". What I want to convey here is that the same kind of rules apply.
reduce may strip out the surrounding structure, values are still bound together in an algebraic structure for the time of the transformation.
the algebra of reducers
Algebraic structures are way out of the scope of this answer, so I will only touch on how they are relevant.
((a ❋ b) ❋ c) ❋ d
Looking at the expression above, it is self-evident that there is a constraint that ties all the values together : ❋ must know how to combine them the same way + must know how to combine 1 + 2 and just as importantly (1 + 2) + 3.
Weakest safe structure
One way to ensure this is to enforce that these values belong to a same set on which the reducer is an "internal" or "closed" binary operation, that is to say: combining any two values from this set with the reducer produces a value which belongs to the same set.
In abstract algebra this is called a magma. You can also look up semi-groups which are more talked about and are the same thing with associativity (no braces required), although reduce doesn't care.
Less safe
Living in a magma is not absolutely necessary : we can imagine a situation where ❋ can combine a and b but not c and b.
An example of this is function composition. One of the following functions returns a string, which constrains the order in which you can combine them:
const a = x => x * 2;
const b = x => x ** 2;
const c = x => x + ' !';
// (a ∘ b) ∘ c
const abc = x => c(b(a(x)));
abc(5); // "100 !"
// (a ∘ c) ∘ b
const acb = x => b(c(a(x)));
acb(5); // NaN
Like many binary operations, function composition can be used as a reducer.
Knowing if we are in a situation where reordering or removing elements from an array could make reduce break is kind of valuable.
So, magmas: not absolutely necessary, but very important.
what about the initial value
Say we want to prevent an exception from being thrown when the array is empty, by introducing an initial value:
array.reduce(f, init)
// which is really the same as doing
[init, ...array].reduce(f)
// or
((init ❋ a) ❋ b) ❋ c...
We now have an additional value. No problem.
"No problem"!? We said the purpose of the reducer was to combine the array values, but init is not a true value: it was forcefully introduced by ourselves, it should not affect the result of reduce.
The question is:
What init should we pick so that f(init, a) or init ❋ a returns a?
We want an initial value which acts as though it was not there. We want a neutral element (or "identity").
You can look up unital magmas or monoids (the same with associativity) which are swear words for magmas equipped with a neutral element.
Some neutral elements
You already know a bunch of neutral elements
numbers.reduce((a, b) => a + b, 0)
numbers.reduce((a, b) => a * b, 1)
booleans.reduce((a, b) => a && b, true)
strings.reduce((a, b) => a.concat(b), "")
arrays.reduce((a, b) => a.concat(b), [])
vec2s.reduce(([u,v], [x,y]) => [u+x,v+y], [0,0])
mat2s.reduce(dot, [[1,0],[0,1]])
You can repeat this pattern for many kinds of abstractions. Note that the neutral element and the computation don't need to be this trivial (extreme example).
Neutral element hardships
We have to accept the fact that some reductions are only possible for non-empty arrays and that adding poor initialisers don't fix the problem.
Some examples of reductions gone wrong:
Only partially neutral
numbers.reduce((a, b) => b - a, 0)
// does not work
numbers.reduce((a, b) => a - b, 0)
Subtracting 0 form b returns b, but subtracting b from 0 returns -b.
We say that only "right-identity" is true.
Not every non-commutative operation lack a symmetrical neutral element but it's a good sign.
Out of range
const min = (a, b) => a < b ? a : b;
// Do you really want to return Infinity?
numbers.reduce(min, Infinity)
Infinity is the only initial value which does not change the output of reduce for non-empty arrays, but it is unlikely that we would want it to actually appear in our program.
The neutral element is not some Joker value we add as a convenience. It has to be an allowed value, otherwise it doesn't accomplish anything.
Nonsensical
The reductions bellow rely on position, but adding an initialiser naturally shifts the first element to the second place, which requires messing with the index in the reducer to maintain the behaviour.
const first = (a, b, i) => !i ? b : a;
things.reduce(first, null);
const camelCase = (a, b, i) => a + (
!i ? b : b[0].toUpperCase() + b.slice(1)
);
words.reduce(camelCase, '');
It would have been a lot cleaner to embrace the fact the array can't be empty and simplify the definition of the reducers.
Moreover, the initials values are degenerate:
null is not the first element of an empty array.
an empty string is by no means a valid identifier.
There is no way to preserve the notion of "firstness" with an initial value.
conclusion
Algebraic structures can help us think of our programs in a more systematic way. Knowing which one we are dealing with can predict exactly what we can expect from reduce, so I can only advise you to look them up.
One step further
We have seen how map and reduce were so different structure-wise, but it is not as though they were two isolated things.
We can express map in terms of reduce, because it is always possible to rebuild the same structure we started with.
const map = f => (acc, x) =>
acc.concat(f(x))
;
const double = x => x * 2;
[1, 2, 3].reduce(map(double), []) // [2, 4, 6]
Pushing it a little further has led to neat tricks such as transducers.
I will not go into much detail about them, but I want you to notice a couple of things which will echo what we have said before.
Transducers
First let's see what problem we are trying to solve
[1, 2, 3, 4].filter(x => x % 2 == 0)
.map(x => x ** 2)
.reduce((a, b) => a + b)
// 20
We are iterating 3 times and creating 2 intermediary data structures. This code is declarative, but not efficient. Transducers attempt to reconcile the two.
First a little util for composing functions using reduce, because we are not going to use method chaining:
const composition = (f, g) => x => f(g(x));
const identity = x => x;
const compose = (...functions) =>
functions.reduce(composition, identity)
;
// compose(a, b, c) is the same as x => a(b(c(x)))
Now pay attention to the implementation of map and filter bellow. We are passing in this reducer function instead of concatenating directly.
const map = f => reducer => (acc, x) =>
reducer(acc, f(x))
;
const filter = f => reducer => (acc, x) =>
f(x) ? reducer(acc, x) : acc
;
look at this more specifically:
reducer => (acc, x) => [...]
after the callback function f is applied, we are left with a function which takes a reducer as input and returns a reducer.
These symmetrical functions is what we pass to compose:
const pipeline = compose(
filter(x => x % 2 == 0),
map(x => x ** 2)
);
Remember compose is implemented with reduce: our composition function defined earlier combines our symmetrical functions.
The output of this operation is a function of the same shape: something which expects a reducer and returns a reducer, which means
we have a magma. We can keep composing transformations as long as they have this shape.
we can consume this chain by applying the resulting function with a reducer, which will return a reducer that we can use with reduce
I let you expand the whole thing if you need convincing. If you do so you will notice that transformations will conveniently be applied left to right, which is the opposite direction of compose.
Alright, lets use this weirdo:
const add = (a, b) => a + b;
const reducer = pipeline(add);
const identity = 0;
[1, 2, 3, 4].reduce(reducer, identity); // 20
We have composed operations as diverse as map, filter and reduce into a single reduce, iterating only once with no intermediary data-structure.
This is no small achievement! And it is not a scheme you can come up with by deciding between map and reduce merely on the basis of the conciseness of the syntax.
Also notice that we have full control over the initial value and the final reducer. We used 0 and add, but we could have used [] and concat (more realistically push performance-wise) or any other data-structure for which we can implement a concat-like operation.
To understand the difference between map, filter and reduce, remember this:
All three methods are applied on array so anytime you want to make any operation on an array, you will be using these methods.
All three follow functional approaches and therefore the original array remains the same. Original array doesn't change instead a new array/value is returned.
Map returns a new array with the equal no. of elements as there are in the original array. Therefore, if the original array has 5 elements, the returned array will also have 5 elements. This method is used whenever we want to make some change on every individual element of an array. You can remember that every element of ann array is being mapped to some new value in output array, therefore the name map
For eg,
var originalArr = [1,2,3,4]
//[1,2,3,4]
var squaredArr = originalArr.map(function(elem){
return Math.pow(elem,2);
});
//[1,4,9,16]
Filter returns a new array with equal/less number of elements than the original array. It returns those elements in the array which have passed some condition. This method is used when we want to apply a filter on the original array therefore the name filter. For eg,
var originalArr = [1,2,3,4]
//[1,2,3,4]
var evenArr = originalArr.filter(function(elem){
return elem%2==0;
})
//[2,4]
Reduce returns a single value, unlike a map/filter. Therefore, whenever we want to run an operation on all elements of an array but want a single output using all elements, we use reduce. You can remember an array's output is reduced to a single value therefore the name reduce. For eg,
var originalArr = [1,2,3,4]
//[1,2,3,4]
var sum = originalArr.reduce(function(total,elem){
return total+elem;
},0)
//10
The map function executes a given function on each element but reduce executes a function which reduces the array to a single value. I'll give an example of both:
// map function
var arr = [1, 2, 3, 4];
var mappedArr = arr.map((element) => { // [10, 20, 30, 40]
return element * 10;
})
// reduce function
var arr2 = [1, 2, 3, 4]
var sumOfArr2 = arr2.reduce((total, element) => { // 10
return total + element;
})
It is true that reduce reduces an array to a single value, but since we can pass an object as initialValue, we can build upon it and end up with a more complex object than what we started with, such as this example where we group items by some criteria. Therefore the term 'reduce' can be slightly misleading as to the capabilities of reduce and thinking of it as necessarily reducing information can be wrong since it could also add information.
let a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
let b = a.reduce((prev, curr) => {
if (!prev["divisibleBy2"]) {
prev["divisibleBy2"] = []
}
if (curr % 2 === 0) {
prev["divisibleBy2"].push(curr)
}
if (!prev["divisibleBy3"]) {
prev["divisibleBy3"] = []
}
if (curr % 3 === 0) {
prev["divisibleBy3"].push(curr)
}
if (!prev["divisibleBy5"]) {
prev["divisibleBy5"] = []
}
if (curr % 5 === 0) {
prev["divisibleBy5"].push(curr)
}
return prev
}, {})
console.log(b)
After years of writing loops in C++ the tedious way
for(int i=0; i<N; ++i) {
...
}
it becomes quite nice to use iterators
for(it i=v.begin(); i<v.end(); ++i) {
...
}
and ultimately moving to range iterators
for(auto i:v) {
...
}
In JavaScript also the for can be used, in a style nearly identical
(minus the type declaration and the pre/post increment operator) to
the first one above.
Still, in all of these the for is there. The D3.js
library demonstrates an alternative. One can iterate over an array by writing
d3.select("body")
.selectAll("p")
.data([4, 8, 15, 16, 23, 42])
.enter().append("p")
.text(function(d) { return "I’m number " + d + "!"; });
Here the enter mutates to a for loop. The documentation
explains nicely the client-side view of joins. What I am missing is a
standalone example of the (functional programming?) style of
converting a function call to an iteration.
No doubt this is not unique to D3.js. This is just where I encountered the idiom.
Can you suggest a few lines of standalone JavaScript code that
demonstrate iteration through a function call?
There are at least a couple of built-in functions that come to my mind.
map()
This one is very obvious.
[1, 2, 3]
.map(someNumber => someNumber * someNumber)
.map((powered, index) => index + "::" + powered);
// --> [ "1::1", "2::4", "3::9" ]
Chains well, right? Takes some input and produces the result consisting of elements calculated by applying a function element-wise.
Recommendation: try to use with pure functions whenever possible (produce the same results for same inputs, don't mutate the original collection if possible, nor produce any side effects).
forEach()
This function iterates through all elements of an array too, and applies a function, without returning anything back. Therefore, it can only end a chain of calls, but cannot be used for further chaining.
[1, 2, 3, 4]
.forEach(number => console.info(number));
Recommendation: forEach() is useful when we want to write some code that will result in a side effect per entry in the collection being iterated.
filter()
Filter function uses a predicate is used to sift the wheat from the chaff. The predicate is defining a criteria for the items you want to deal with on the next "stage".
[null, undefined, 0, 1, 2, 3, NaN, "", "You get the idea"]
.filter(Boolean)
.map(filteredElement => filteredElement + "!")
// --> [ "1!", "2!", "3!", "You get the idea!" ]
Recommendation: try to use with pure functions whenever possible. I.e. don't do anything else in filter other than things immediately related to filtration logic itself.
Object.keys() and Object.entries()
These two functions are helpful when we need to iterate over object's keys or key-value pairs, rather than an array's elements.
const targetObject = { a: 1, b: 2, c: 3 };
Object
.keys(targetObject)
.map(key => key + "=" + targetObject[key])
// --> [ "a=1", "b=2", "c=3" ]
same result can be achieved like this
Object
.entries({ a: 1, b: 2, c: 3 })
.map((key, value) => key + "=" + value)
// --> [ "a=1", "b=2", "c=3" ]
Recommendation: you may want to use Object.hasOwnProperty(...) when using working with Object.keys(...). See the documentation for details.
find()
The one is almost trivial. Let's us search for an item that matches a predicate. The search is "left-to-right", and it stops whenever the first "match" is found.
[1, 5, 10, 15]
.find(number >= 7)
// --> 10
findIndex() function can be used when we're looking for a position of an element that matches a predicate.
some() and every()
These functions check whether
a) there is at least one element matching a predicate; or
b) each and every element is matching a predicate.
const arrayOfNumbers = [2, 4, 6, 8, 10];
arrayOfNumbers.every(number => number % 2 === 0); // --> true
arrayOfNumbers.every(number => number % 2 === 1); // --> false
arrayOfNumbers.some(number => number > 1); // --> true
arrayOfNumbers.some(number => number <= 1); // --> false
reduce() and `reduceRight()`
The last one to mention in this quick review is the function that takes a list of things and aggregates it into a single result.
[-1, 0, 1, 2, 3]
.filter(value => value >= 0) // [0, 1, 2, 3]
.map(value => value + 1) // [1, 2, 3, 4]
.reduce((subTotal, currentValue) => subTotal + currentValue, 5);
// --> 15
Recommendation: try to use with pure functions whenever possible.
Universally applicable note on performance. In my benchmarks (don't have them on hand), a hand-written for loop was always faster than forEach, map, and other iterating functions. I do still prefer the functions unless the performance is being severely affected. There two main reasons for that: 1) easier to avoid off-by-one-errors; 2) the code is more readable, since each single function defines an independent step in the data processing flow, thus making code simpler and more maintainable.
I hope, this is an okay overview of some built-in chain-able JavaScript functions. More are described here. Take a look at concat(), sort(), fill(), join(), slice(), reverse() -- I frequently use them too.
If you need something like first() or last(), you will not find them in native functions. Either write your own ones, or use third-party libraries (e.g. lodash, rambda.js).
Here is an example implementation of Array.prototype.forEach:
function foreach(array, cb) {
for (var i = 0; i < array.length; ++i)
cb(array[i], i, array);
}
foreach([2,8,739,9,0], (n, i) =>
console.log("number: %s\nindex: %s\n", n, i));
surely I don't have to spoonfeed you do I?
function array_iterator(array) {
var i = 0;
function next() {
return array[i++];
}
function head() {
return array[i];
}
function tail() {
return array[array.length-1];
}
function more() {
return i < array.length;
}
function done() {
return !more();
}
function reset() {
i = 0;
}
return { next, head, tail, done, more, reset };
}
var nums = [3,34,4];
var iter = array_iterator(nums);
while (iter.more()) {
console.log(iter.next());
}
function each(coll, f) {
if (Array.isArray(coll)) {
for (var i = 0; i < coll.length; i++) {
f(coll[i], i);
}
} else {
for (var key in coll) {
f(coll[key], key);
}
}
}
function map(array, f) {
var acc = [];
each(array, function(element, i) {
acc.push(f(element, i));
});
return acc;
}
function max(numbers) {
var maximum = numbers[0];
each(numbers,function(x){
if(x>maximum){
maximum = x;}
});
return maximum;
}
function maximums(arrays){
return map(arrays, function(x){
return max(arrays);
})
}
maximums([1,2,3],[5,6,7])
I don't quite understand map. I wrote a maximum number in an array function with each and I want to implement that to map. I know I am not returning the right thing with return max(arrays) but I've also tried return max(arrays[x]) to make an emphasize that I want to iterate through the whole argument of arrays. return max(arrays) ==> [3,3,3], returns the largest number in one array 3 times which i dont understand why either.
You need to get max from x
function maximums(arrays) {
return map(arrays, function (x) {
return max(x);
// ^
});
}
And call the function with an array of arrays
maximums([[1, 2, 3], [5, 6, 7]]);
// ^ ^
function each(coll, f) {
if (Array.isArray(coll)) {
for (var i = 0; i < coll.length; i++) {
f(coll[i], i);
}
} else {
for (var key in coll) {
f(coll[key], key);
}
}
}
function map(array, f) {
var acc = [];
each(array, function (element, i) {
acc.push(f(element, i));
});
return acc;
}
function max(numbers) {
var maximum = numbers[0];
each(numbers, function (x) {
if (x > maximum) {
maximum = x;
}
});
return maximum;
}
function maximums(arrays) {
return map(arrays, function (x) {
return max(x);
})
}
console.log(maximums([[1, 2, 3], [5, 6, 7]]));
Your code is based off of a bad building block (each) and therefore the rest of your code suffers the consequences. My answer will start you off with a better building block, reduce (aka foldLeft), and show you how to build up from there.
What you'll notice about this answer is that we break each function down into really simple pieces and then use higher-order functions to put everything together.
avoid use of each which limits us to side-affecting functions
no need to check for Array.isArray
no imperative style for-loops with mutable iterators, i
no need to check array .length property
I highly encourage you to step thru the evaluation of this code and see how all of the parts work. If you can get an understanding of how either of these programs work, you'll be well on your way to mastering some of most important fundamentals of functional programming: recursion, immutability, referential transparency, higher-order functions, currying, and function composition†
ES6 offers arrow functions, destructuring assignment, and spread syntax which makes functional programs a breeze to write in JavaScript – tho it will feel quite different to read, at first. I'll offer pre-ES6 below this snippet.
Related: What do multiple arrow functions mean in JavaScript?
// reduce is your new, gold-standard building block
const reduce = f => y => ([x,...xs]) => {
if (x === undefined)
return y
else
return reduce (f) (f (y) (x)) (xs)
}
// derive map from reduce
const map = f => reduce (acc => x => [...acc, f(x)]) ([])
// get max of 2 numbers
const max = x => y => x > y ? x : y
// get max of a list of numbers
const maximum = reduce (max) (-Infinity)
// get each max of a list of a list of numbers
const maximums = map (maximum)
// see the result of your hard work
console.log(maximums ([ [ 1, 3, 2 ], [ 7, 5, 6 ] ]))
// => [ 3, 7 ]
If you're struggling with the code above, this code (below) is written in ES5 (more accurately, pre-ES6). The more familiar syntax might help you along while you're still cutting your teeth. It's more verbose in ES5 but it works (almost) identically.
// reduce is your new, gold-standard building block
function reduce (f) {
return function (y) {
return function (xs) {
if (xs.length === 0)
return y
else
return reduce (f) (f (y) (xs[0])) (xs.slice(1))
}
}
}
// derive map from reduce
function map (f) {
return reduce (function (acc) {
return function (x) {
return acc.concat([ f(x) ])
}
}) ([])
}
// get max of 2 numbers
function max (x) {
return function (y) {
return x > y ? x : y
}
}
// get max of a list of numbers
var maximum = reduce (max) (-Infinity);
// get each max of a list of a list of numbers
var maximums = map (maximum);
// see the result of your hard work
console.log(maximums ([ [ 1, 3, 2 ], [ 7, 5, 6 ] ]))
// => [ 3, 7 ]
† Of course there are other fundamentals of functional programming, but this is certainly a good start
Still stuck?
reduce is inarguably the most sophisticated function in the programs above. If you're struggling with it, we can cheat a little bit to short-cut our understanding of the program. JavaScript offers a built-in Array.prototype.reduce which (almost) works the same. We could write our reduce using JavaScript's and get the rest for free !
Just one more thing. JavaScript's reduce expects a binary function but the rest of our program is expecting curried functions (sequences of unary functions). To get around that, we will first make a small uncurry combinator to fit everything together
// convert sequence of unary functions to a binary function
const uncurry = f => (x,y) => f (x) (y)
// we can cheat using JavaScript built-in reduce with uncurry
const reduce = f => y => xs => xs.reduce(uncurry(f), y)
// the rest stays the same !
// ...
// derive map from reduce
const map = f => reduce (acc => x => [...acc, f(x)]) ([])
// get max of 2 numbers
const max = x => y => x > y ? x : y
// get max of a list of numbers
const maximum = reduce (max) (-Infinity)
// get each max of a list of a list of numbers
const maximums = map (maximum)
// see the result of your hard work
console.log(maximums ([ [ 1, 3, 2 ], [ 7, 5, 6 ] ]))
// => [ 3, 7 ]
Extra credit 1
So I told you reduce is your Gold Standard building block. Aside from map, did you know that you can also use reduce to implement countless other functions? Some include: filter, find, some, every, keys, and entries.
Extra credit 2
Some functions mentioned in Extra Credit 1 should short-circuit when the final answer can be returned before the end of the array is reached. Which ones can be short-circuited? And how can we rewrite reduce to facilitate this early-exit behaviour?
Have you tried return max(x);? I think that might be what you are wanting. Also there is a Max() in javascript so you do not need to define it:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max
You don't need to roll your own implementation for most of these functions. Use Math.max in conjunction with Array#map like this:
function maximum (array) {
return Math.max.apply(null, array)
}
console.log(
[[1,2,3], [5,6,7]].map(maximum)
) //=> [3, 7]
// if you still want a `maximums` function
function maximums (arrays) {
return arrays.map(maximum)
}
console.log(
maximums([[1,2,3], [5,6,7]]))
) //=> [3, 7]
You can combine map and reduce functions:
function maximums(x) {
return x.map(function(y){
return y.reduce(function(a,b){ return Math.max(a,b); });
});
}
Consider the working code below:
var randN = x => () => Math.floor(x*Math.random());
var rand10 = randN(10)
times(rand10, 10) // => [6, 3, 7, 0, 9, 1, 7, 2, 6, 0]
randN is a function that takes a number and returns an RNG that, when called, will return a random int in the range [0, N-1]. So it's a factory for specific RNGs.
I've been using ramda.js, and learning functional programming theory, and my question is: Is it possible to rewrite randN in a point free style using ramda?
For example, I could write:
var badAttempt = pipe(multiply(Math.random()), Math.floor)
This would satisfy the "point-free style" requirement, but fails to behave the same way as randN: calling badAttempt(10) simply returns a single random number between 1 and 10, rather than a function that generates a random number between 1 and 10 when called.
I have not been able to find a combination of ramda functions that enables me to do the rewrite in a point-free style. I can't tell if this is just a failure on my part, or something special about using random, which breaks referential transparency and therefore may be incompatible with a point free style.
update
my own slight variation on the solution, after discussing it with Denys:
randN = pipe(always, of, append(Math.random), useWith(pipe(multiply, Math.floor)), partial(__,[1,1]))
This would help with an extra function for abstracting a function to re-evaluate its arguments each time it is called.
thunk = fn => R.curryN(fn.length, (...args) => () => fn(...args))
The only purpose of this function would be to cause some side effect within the given fn function.
Once we have thunk function, we can define randN like so:
randN = thunk(R.pipe(S.S(R.multiply, Math.random), Math.floor))
R.times(randN(10), 5) // e.g. [1, 6, 9, 4, 5]
Note: S.S here is the S combinator from Sanctuary which does effectively the same thing as R.converge(multiply, [Math.random, identity]).
I do however only recommend going with a point-free solution if it actually improves the readability of a function.
I don't know if it's a good idea to learn functional programming using a specific library, because the characteristics of a lib and the functional paradigm will mix inevitably. In practice, however, Ramda is incredibly useful. It bridges the gap between the imperative reality and the functional Fantasy Land in Javascript :D
Here's a manual approach:
// a few generic, reusable functions:
const comp = f => g => x => f(g(x)); // mathematical function composition
const comp2 = comp(comp)(comp); // composes binary functions
const flip = f => x => y => f(y)(x); // flips arguments
const mul = y => x => x * y; // first class operator function
// the actual point-free function:
const randN = comp2(Math.floor)(flip(comp(mul)(Math.random)));
let rand10 = randN(10); // RNG
for (let i = 0; i < 10; i++) console.log(rand10());
It's worth mentioning that randN is impure, since random numbers are impure by definition.
var randN = R.converge(R.partial, [R.wrap(R.pipe(R.converge(R.multiply, [Math.random, R.identity]), Math.floor), R.identity), R.of])
var rand10 = randN(10)
alert(R.times(rand10, 10)) // => [3, 1, 7, 5, 7, 5, 8, 4, 7, 2]
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.19.1/ramda.js"></script>