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$
    – Acus
    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$
    – theorist
    Dec 14, 2021 at 8:11
  • 4
    $\begingroup$ Through[{StringJoin, Length}[{##}]] & @@@ s2 $\endgroup$
    – Acus
    Dec 14, 2021 at 8:38
  • 3
    $\begingroup$ @Acus, please convert your comments into a more detailed answer. $\endgroup$
    – Syed
    Dec 14, 2021 at 9:28
  • 2
    $\begingroup$ Why not just Through[{StringJoin, Length}[#]]&/@s2 ? $\endgroup$
    – SquareOne
    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$ 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$
    – Carl Woll
    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$ 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$
    – theorist
    Dec 17, 2021 at 5:45
18
$\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$
12
$\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$
5
$\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$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.