12
$\begingroup$

Through allows multiple functions to be easily applied to a single argument. For instance, suppose I wanted to count the number of characters in {"t", "o", "d", "a", "y"}, and also join them into a single string. Then I could do:

s1 = {"t", "o", "d", "a", "y"} Through[{StringJoin, Length}[#]]& @ s1 

{today, 5}

But suppose I wanted to do this for a list of lists. Then I would need to use Map to apply the functions to each sublist. I could accomplish that with this:

s2 = {{"h", "e", "l", "l", "o"}, {"d", "a", "y"}} Thread@ MapThread[# /@ s2 &, {{StringJoin, Length}}] 

{{hello, 5}, {day, 3}}

But that syntax is a bit ugly. It would be simpler and more elegant if there were a function that could do for Map what Through does for Apply, i.e. that allowed one to directly Map multiple functions to a single argument, like so (here I've called the hypothetical function MultiMap):

MultiMap[{StringJoin, Length}[#]]& @ s2 

{{hello, 5}, {day, 3}}

Does such a function exist?

$\endgroup$
6
  • 5
    $\begingroup$ {StringJoin[##], Length[{##}]} & @@@ s2 $\endgroup$ Commented Dec 14, 2021 at 7:41
  • $\begingroup$ @Acus That's nice, and I'll keep it in mind, but it lacks the essential feature of Through, which is that it allows one to apply two different functions to a single argument, rather than having to repeat the argument for each function. $\endgroup$ Commented Dec 14, 2021 at 8:11
  • 4
    $\begingroup$ Through[{StringJoin, Length}[{##}]] & @@@ s2 $\endgroup$ Commented Dec 14, 2021 at 8:38
  • 3
    $\begingroup$ @Acus, please convert your comments into a more detailed answer. $\endgroup$ Commented Dec 14, 2021 at 9:28
  • 2
    $\begingroup$ Why not just Through[{StringJoin, Length}[#]]&/@s2 ? $\endgroup$ Commented Dec 21, 2021 at 11:45

4 Answers 4

12
$\begingroup$
ClearAll[mapThrough] mapThrough[a_List] := Map[Through @ a @ ## &] 

or, as suggested by J.M. in comments,

mapThrough[a_] := Map @ Through @* a 

or

mapThrough = a |-> Map @ Through @* a 

Examples:

s2 = {{"h", "e", "l", "l", "o"}, {"d", "a", "y"}}; mapThrough[{StringJoin, Length}] @ s2 
{{"hello", 5}, {"day", 3}} 

Use with an arbitrary list of functions:

mapThrough[{StringReverse @* StringJoin, Reverse, First, Last, #[[{1, -1}]&}] @ s2 
{{"olleh", {"o", "l", "l", "e", "h"}, "h", "o", {"h", "o"}}, {"yad", {"y", "a", "d"}, "d", "y", {"d", "y"}}} 
$\endgroup$
4
  • 8
    $\begingroup$ I use the following variation myself rather frequently: Through @* {StringJoin, Length} /@ {{"h", "e", "l", "l", "o"}, {"d", "a", "y"}} $\endgroup$ Commented Dec 14, 2021 at 14:20
  • $\begingroup$ @J.M. I think you should post this as an answer, I really like operator forms that avoid using slots. $\endgroup$ Commented Dec 14, 2021 at 17:27
  • $\begingroup$ @Carl, this is effectively equivalent to kglr's approach (with the advantage you mention of being slot-free), so I would prefer that kglr edit his answer to include it. $\endgroup$ Commented Dec 14, 2021 at 17:32
  • $\begingroup$ @J.M. I found all the answers here great—and instructive. But I have to say yours was my favorite, because it accomplishes the task with the simplest possible syntax: Going from "multiApply" to "multiMap" just requires changing from Through[{func1, func2, func3...}[s2]] to Through@*{func1, func2, func3...}/@s2. And it cleverly accomplishes this by composing func 1, func2,.. with Throughto create a new composite function, and then mapping that composite function to the arguments. Since you donated your syntax to kglr's answer, I'll give it the credit :). $\endgroup$ Commented Dec 17, 2021 at 5:45
19
$\begingroup$

One option is to use the forking operator {fn1, fn2, ...} from the Query sublanguage:

Query[All, {StringJoin, Length}][s2] (* {{hello,5},{day,3}} *) 
$\endgroup$
13
$\begingroup$

First, the shortcut @@@ is equivalent to "Map Apply" or simply speaking apply to level 1:

Apply[f, {{1, 2}, {a, b}}, {1}] 

{f[1, 2], f[a, b]}

f@@@{{1, 2}, {a, b}} 

{f[1, 2], f[a, b]}

Second, {##} takes all arguments and puts into List on which then pure function f = Through[{StringJoin, Length}[#]]& is applied. In general you can imagine functional programming like an engineering line conveyor which produces something you want from elementary operations.

$\endgroup$
6
$\begingroup$

Use the operator form of Map and new-in-14.0 Comap:

Map[Comap@{StringJoin, Length}]@s2 (* {{"hello", 5}, {"day", 3}} *) 

enter image description here

$\endgroup$

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.