6

Given the following arrays:

const array1 = ["a1", "b1", "c1", "d1"],
      array2 = ["a2", "b2"],
      array3 = ["a3", "b3", "c3"]

Is there any ramda function to simplify the following scenario on which I could give one or more arrays?

const nestedMap = map => {
    const result = []

    for(let item1 of array1) 
        for(let item2 of array2)
            for(let item3 of array3)
                    result.push(map(item1, item2, item3))
    return result
}

Whole function would look as follows:

// Sample usage
nestedMap((item1, item2, item3) => `${item1} ${item2} ${item3}`, array1, array2, array3)

I'm looking to avoid reinventing the wheel.

Note: Vanilla javascript or any other library can be acceptable. I initially talked about ramda as it has a lot of functions and maybe I've missed which could assist on solving this problem

Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
  • So, essentially we are looking for a matrix formation. Correct? It would help if you can also add a sample output. Also, is pure JS functions acceptable? – Rajesh Oct 10 '17 at 11:38
  • Since you are asking about functional programming: why does your function not have a return value? I don't see any `map`ping going on here, this looks more like a multi-dimensional `forEach` with a callback. – Bergi Oct 10 '17 at 11:43
  • This is called the [cartesian product](https://stackoverflow.com/a/15310051/1048572) – Bergi Oct 10 '17 at 11:44
  • @Bergi Sorry, I've fixed my Q. Now it's already returning – Matías Fidemraizer Oct 10 '17 at 13:08
  • @Rajesh Why not. I want to be clear that I'm not looking to get the source code of how to do it using vanilla JS but if you've something done already it may be acceptable too – Matías Fidemraizer Oct 10 '17 at 13:09
  • @MatíasFidemraizer But that doesn't make any sense now, it's calling the `map` callback exactly once. I'm certain you don't want that. A [mapping function](https://en.wikipedia.org/wiki/Map_(higher-order_function)) should preserve the structure. What do you expect the result to look like? – Bergi Oct 10 '17 at 13:11
  • @Bergi Oops, fixed. I know I could use a generator too, but it's just a code snippet to understand the issue. – Matías Fidemraizer Oct 10 '17 at 13:14
  • @MatíasFidemraizer Yes, you could just [compose some generators](https://stackoverflow.com/a/45735702/1048572) as well. Or use Ramda's `concatMap`/`chain` – Bergi Oct 10 '17 at 13:16
  • @Bergi See my own answer, what do you think? – Matías Fidemraizer Oct 10 '17 at 14:56

4 Answers4

5

You can use the applicative instance for arrays here to simply R.lift your function:

const array1 = ["a1", "b1", "c1", "d1"],
      array2 = ["a2", "b2"],
      array3 = ["a3", "b3", "c3"]

const nestedMap = R.lift((item1, item2, item3) => `${item1} ${item2} ${item3}`)

console.log(nestedMap(array1, array2, array3))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
Scott Christopher
  • 6,458
  • 23
  • 26
3

Ramda has a xprod function that gives the cross-product of two lists. It's relatively straightforward to extend it to multiple lists like this:

const xproduct = R.reduce(R.pipe(R.xprod, R.map(R.unnest)), [[]])

Then we can use this to create the nested map function relatively easily:

const array1 = ["a1", "b1", "c1", "d1"],
      array2 = ["a2", "b2"],
      array3 = ["a3", "b3", "c3"]

const xproduct = R.reduce(R.pipe(R.xprod, R.map(R.unnest)), [[]])
const nestedMap = (fn, ...arrs) => R.map(R.apply(fn), xproduct(arrs))

console.log(nestedMap((a, b, c) => `${a}-${b}-${c}`, array1, array2, array3))
//==> ["a1-a2-a3", "a1-a2-b3", ...]
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
2

You could use a two step approach,

  1. build all products
  2. map the function.

const
    nestedMap = (fn, ...array) => array
        .reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []))
        .map(a => fn(...a)),
    array1 = ["a1", "b1", "c1", "d1"],
    array2 = ["a2", "b2"],
    array3 = ["a3", "b3", "c3"],
    result = nestedMap((item1, item2, item3) => `${item1} ${item2} ${item3}`, array1, array2, array3)

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
2

how it works

This answer gives you insight on how @ScottChristopher's reply is working by showing you .ap (and .chain) implemented directly on Array's prototype – as an exercise! Don't freak out, prototype fanatics...

The idea here is demonstrate the goal behavior/output in a code snippet that shows you all of the moving parts at once. With only ~8 lines of code to understand, the intimidation factor is quite low; compared to something like digging in the Rambda source (which is actually pretty nice)

I shared another answer recently that does something very similar using delimited continuations – if this answer is interesting to you, I think you will also enjoy that reading that one ^_^

// Array Applicative  
Array.prototype.ap = function (...args)
  {
    const loop = (acc, [x,...xs]) =>
      x === undefined
        ? [ this [0] (...acc) ]
        : x.chain (a =>
            loop (acc.concat ([a]), xs))
    return loop ([], args)
  }
 
// Array Monad
Array.prototype.chain = function chain (f)
  {
    return this.reduce ((acc, x) =>
      acc.concat (f (x)), [])
  }

const array1 = ['a1', 'b1', 'c1', 'd1']
const array2 = ['a2', 'b2']
const array3 = ['a3', 'b3', 'c3']

console.log ([ (x,y,z) => [x,y,z] ] .ap (array1, array2, array3))
// [ [ 'a1', 'a2', 'a3' ],
//   [ 'a1', 'a2', 'b3' ],
//   [ 'a1', 'a2', 'c3' ],
//   [ 'a1', 'b2', 'a3' ],
//   [ 'a1', 'b2', 'b3' ],
//   [ 'a1', 'b2', 'c3' ],
//   [ 'b1', 'a2', 'a3' ],
//   [ 'b1', 'a2', 'b3' ],
//   [ 'b1', 'a2', 'c3' ],
//   [ 'b1', 'b2', 'a3' ],
//   [ 'b1', 'b2', 'b3' ],
//   [ 'b1', 'b2', 'c3' ],
//   [ 'c1', 'a2', 'a3' ],
//   [ 'c1', 'a2', 'b3' ],
//   [ 'c1', 'a2', 'c3' ],
//   [ 'c1', 'b2', 'a3' ],
//   [ 'c1', 'b2', 'b3' ],
//   [ 'c1', 'b2', 'c3' ],
//   [ 'd1', 'a2', 'a3' ],
//   [ 'd1', 'a2', 'b3' ],
//   [ 'd1', 'a2', 'c3' ],
//   [ 'd1', 'b2', 'a3' ],
//   [ 'd1', 'b2', 'b3' ],
//   [ 'd1', 'b2', 'c3' ] ]
Mulan
  • 129,518
  • 31
  • 228
  • 259