norbertpy - 2 years ago 54
Javascript Question

# Functional way for custom iteration

How can I use only

`map`
,
`reduce`
or
`filter`
or any functional way to create a custom iteration on an array?

Let's say I wanna map an array to another array which holds the sum of each three adjacent elements in the source array:

``````var source = [1, 2, 3, 4, 6, 7, 8] // to [6, 17, 8]
``````

Or make a bucket of two elements:

``````var source = [1, 2, 3, 4, 5, 6, 7] // to [[1, 2], [3, 4], [5, 6], [7]]
``````

For the second one I have the following but that doesn't look very functional as I'm accessing array by index:

``````function* pairMap(data) {
yield* data.map((item, index) => {
if (index > 0) {
return [data[index - 1], item];
}
});
}
``````

I'm interested in the functional way of doing this.

Answer Source

Let's say I wanna map an array to another array which holds the sum of each three adjacent elements in the source array:

``````var source = [1, 2, 3, 4, 6, 7, 8] // to [6, 17, 8]
``````

Maps create 1:1 relationships, so this wouldn't be appropriate use of `map`. Instead, a `reduce` or ("fold") would be better here.

``````const comp = f=> g=> x=> f (g (x));
const len = xs=> xs.length;
const isEmpty = xs=> len(xs) === 0;
const concat = xs=> ys=> ys.concat(xs);

const chunk= n=> xs=>
isEmpty (xs)
? []
: concat (chunk (n) (xs.slice(n))) ([xs.slice(0,n)]);

const reduce = f=> y=> xs=> xs.reduce((y,x)=> f(y)(x), y);
const map = f=> xs=> xs.map(x=> f(x));
const add = x=> y=> y + x;
const sum = reduce (add) (0);

var source = [1, 2, 3, 4, 6, 7, 8];
comp (map (sum)) (chunk (3)) (source);
//=> [ 6, 17, 8 ]
``````

So as you can see, we first transform the `source` into chunks of 3, then we `map` the `sum` function over each chunk.

When you hear people talking about "declarative" code, the last line is quite clear and worries little about implementation. We're not telling the computer how to do its job. There's no `for`/`while` loops, no extraneous vars or iterators, no logic, etc.

"idgaf how, just break `source` up into groups of 3, and then sum each part"

``````// very declaration, wow
comp (map (sum)) (chunk (3)) (source);
``````

Or make a bucket of two elements:

``````var source = [1, 2, 3, 4, 5, 6, 7] // to [[1, 2], [3, 4], [5, 6], [7]]
``````

Using the same code above

``````var source = [1, 2, 3, 4, 5, 6, 7];
chunk (2) (source);
// => [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ], [ 7 ] ]
``````

For the second one I have the following but that doesn't look very functional as I'm accessing array by index:

``````function* pairMap(data) {
yield* data.map((item, index) => {
if (index > 0) {
return [data[index - 1], item];
}
});
}
``````

Using the code above, you can implement `pairMap` easily

``````const pairMap = f=> comp (map (f)) (chunk (2));

var source = [1, 2, 3, 4, 5, 6, 7];
pairMap (pair => console.log(pair)) (source);
// [ 1, 2 ]
// [ 3, 4 ]
// [ 5, 6 ]
// [ 7 ]
``````

Learn all the things

The question is "functional way for custom iteration". You'll notice my code sorta cheats by using `Array.prototype.reduce` and `Array.prototype.map`. Learning how to build these on your own was a good learning tool for me to understand that building functional loops/iterators/control is fun and simple

``````const isEmpty = xs=> xs.length === 0
const head = xs=> xs[0];
const tail = xs=> xs.slice(1);

const reduce = f=> y=> xs=>
isEmpty (xs)
? y
: reduce (f) (f (y) (head (xs))) (tail (xs));

const add = x=> y=> y + x;
reduce (add) (0) ([1,2,3]);
//=> 6
``````

It works!.

OK, let's see how we'd do map

``````const concat = xs=> ys=> ys.concat(xs);
const append = x=> concat ([x]);

const map = f=>
reduce (ys=> x=> append (f (x)) (ys)) ([]);

const sq = x => x * x;
map (sq) ([1,2,3])
//=> [ 1, 4, 9 ]
``````

Quiz 1: Can you write `filter`, `some`, and `every` using `reduce` ?

Troll warning: There's many different ways to implement these functions. If you start writing recursive functions, the first thing you'll want to learn about is what a tail call is. ES6 is getting tail call optimization but it won't be widespread for a while. For a while, Babel could transpile it using a while loop, but it's temporarily disabled in version 6 and will be coming back once they fix it.

Quiz 2: How can you rewrite my `reduce` with a proper tail call?

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download