1

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.

Sam R.
  • 16,027
  • 12
  • 69
  • 122
  • 6
    There's a bunch of ways and those are two very separate problems. What have you tried? – Mike Cluck May 16 '16 at 20:54
  • 3
    Functional does not mean only using `map`, `reduce` and `filter`. Functional means it is declarative and where functions are first-class citizens afaik. – Willem Van Onsem May 16 '16 at 20:56
  • @WillemVanOnsem, that's my question. How do you do this in a `declarative` way? – Sam R. May 16 '16 at 20:58
  • When you figure out 2 you'll have a better understanding how you might get 1. – Andy May 16 '16 at 21:00
  • Are you against just using a "normal" `for` loop? `var output = []; for(var i = 0; i < source.length; i+=2){ output.push([source[i], source[i+1]]); }` (or something like that) – gen_Eric May 16 '16 at 21:02
  • Also, your `pairMap` function won't give the output you want. The 1st iteration will be `undefined` and you will get pairs like `1, 2`, `2, 3`, etc. – gen_Eric May 16 '16 at 21:06

3 Answers3

8

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?

Mulan
  • 129,518
  • 31
  • 228
  • 259
2

For first part of question you can use reduce and slice to group every 3 elements from array and then you can use map and reduce to get sum of each group.

var source = [1, 2, 3, 4, 6, 7, 8];

var result = source.reduce((r, elem, i) => {
  if(i % 3 == 0) r.push(source.slice(i, i+3));
  return r;
}, []);

result = result.map(e => {return e.reduce((a, el) => { return a + el })});
console.log(result)

And for second part of question, again you can use reduce with slice to group every 2 elements.

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

source = source.reduce((r, e, i) => {
  if(i % 2 == 0) r.push(source.slice(i, i+2));
  return r;
}, [])

console.log(source)
Nenad Vracar
  • 118,580
  • 15
  • 151
  • 176
1

First task with one reduce

var source = [1, 2, 3, 4, 5, 6, 7];

var r1 = source.reduce((p, c, i, a) => {
    if (i % 2) p[p.length - 1].push(c);
    else p.push([a[i]]);
    return p;
}, []);

console.log(JSON.stringify(r1, 0, 2));

Second task with reduce and map

var source = [1, 2, 3, 4, 5, 6, 7];

var r2 = source.reduce((p, c, i, a) => {
    if (p[p.length - 1].length < 3) p[p.length - 1].push(c);
    else p.push([a[i]]);
    return p;
}, [[]]).map(e => e.reduce((a, b) => a + b));

console.log(JSON.stringify(r2, 0, 2));
isvforall
  • 8,768
  • 6
  • 35
  • 50