6

I often need to map a list of functions (processors) to several arrays(channels) of float data) so I have written a helper function...

const mapMany = function(processors, channels){
  processors.forEach( function(processor){
    channels = channels.map( (channel) => channel.map(processor) );
  });
  return channels;
};

This reads OK (to me at least!) but mapping an array of functions over another array seems like such a generic thing I can't help but wonder if it IS "a thing" already i.e. is there a better / built in / canonical way of implementing this "Map Many" type functionality and if so what is the proper name for it?

4 Answers 4

5

Yes, there is a better approach for implementing this. Don't use forEach!

function mapMany(processors, channels) {
    return processors.reduce((channels, processor) =>
        channels.map(channel => channel.map(processor))
    , channels);
}

But no, there is no builtin for this nor a canonical name for it. It's a quite specific function, which can however be trivially composed of the standard building blocks.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks. I thought I had already tried that but it turns out I had passed reduce it's parameters the wrong way round!
4

I think you are looking for compose. It looks something like this:

const compose = function (...fns) {
    const rest = fns.reverse();
    const first = rest.shift();
    return function (...args) {
        return rest.reduce((acc, f)=>f.call(this, acc), first.apply(this, args));
    };
};

Now you can compose functions like this:

const stringDouble = compose(String, x=>x*2);
stringDouble("44"); //==> "88"

["22","33","44"].map(stringDouble);
//=> ["44", "66", "88"]

And in your case you can write your function like this:

const mapMany = function(processors, channels){
  // compose iterates from last to first so i apply reverse
  const fun = compose.apply(undefined, processors.reverse());
  return channels.map(fun);
}; 

The advantage over your own code and the other answer using reduce is that this does not make processors.length arrays in the process but just the one.

There are libraries that supplies compose. It's a common function in functional programming.

Other mapping functions like the ones in Underscore lets you set this. Then class methods will work as I pass this to the underlying functions as well.

3 Comments

map(f) . map(g) = map(f . g) is an important and useful identity.
Thanks. That looks lovely. Coming from decades of imperative coding it might take me a while to grok it but I'm going to stare at it until I do!
@technicalbloke It's not that difficult. Basically compose(a,b,c) is the same as (...args)=>a(b(c(...args))) so it's just more general.
1

So as Bergi points out it was simply reduce I was looking for, splitting it into two functions makes it much clearer...

const applySingleProcessor = function(channels, processor){
  return channels.map( channel => channel.map(processor) );
};

const applyMultipleProcessors = function(processors, channels) {
  return processors.reduce(applySingleProcessor, channels);
};

Yay simplicity!

Comments

0

While I would normally use .reduce() as @Bergi did, just for a variety here is a simple recursive .map() solution without .reduce();

var channels = [[1,2,3,4],[5,6,7,8], [857,1453,1881,1071]],
  processors = [x => x+1, x => x*x, x => Math.sqrt(x), x => x-1],
     mapMany = (processors, channels) => processors.length ? (channels = channels.map(c => c.map(processors[0])),
                                                              mapMany(processors.slice(1),channels))
                                                           : channels;
console.log(mapMany(processors,channels));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.