# How to apply Map when the shape of the argument depends on the input?

I would like to apply Map[f] (for some function f) to the output of another function, which usually outputs a list. But that other function is defined using Piecewise, and so depending on the input, mathematica may not be able to determine which case to apply. When that happens, Map[f] does not apply f to the list elements, but to the arguments of Piecewise, which is not what I want.

In case the above explanation is not clear, here is an example of the behavior that I am talking about:

f[x_] := x^2
li[n_] := Piecewise[{{Range[n, n + 10], n > 0}}, Range[-n, -n + 10]]
li[5]
(* {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} *)

li[-5]
(* {1, 2, 3, 4, 5} *)

f /@ li[-5]
(* {25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225} *)

li[n] // InputForm

(* Piecewise[
{{{n, 1 + n, 2 + n, 3 + n, 4 + n, 5 + n, 6 + n, 7 + n, 8 + n, 9 + n, 10 + n}, n > 0}},
{-n, 1 - n, 2 - n, 3 - n, 4 - n, 5 - n, 6 - n, 7 - n, 8 - n, 9 - n, 10 - n}
] *)

f /@ li[n] // InputForm
(* Piecewise[
{{{n^2, (1 + n)^2, (2 + n)^2, (3 + n)^2, (4 + n)^2, (5 + n)^2, (6 + n)^2, (7 + n)^2, (8 + n)^2, (9 + n)^2, (10 + n)^2}, (n > 0)^2}},
{n^2, (1 - n)^2, (2 - n)^2, (3 - n)^2, (4 - n)^2, (5 - n)^2, (6 - n)^2, (7 - n)^2, (8 - n)^2, (9 - n)^2, (10 - n)^2}
]  *)

f /@ li[n] /. n -> -5 // InputForm
(* Piecewise[{{{25, 16, 9, 4, 1, 0, 1, 4, 9, 16, 25}, False^2}},
{25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225}] *)


I understand why this happens, as the Map is evaluated before the rule application. I know that in this case, I can apply the rule before mapping f. But in general, this is not something that I can do. For example, I might want to define a function g by:

g[n_] := f /@ li[n]


Then calling g[x] with a variable x (rather than a concrete number) fails.

I thought of applying Listable to f. However, in my particular case, f is a function that turns lists into other lists, so Listable is not desirable. Something like that:

f[{x_, y_}] := {x - y, x + y}
SetAttributes[f, Listable]
f[{1, 2}]
(* {f[1], f[2]} *)

• Its the execution order which needs to be modified. You can enclose li[n] replacement with brackets like this: f /@ (li[n] /. n -> 5). Mar 29, 2022 at 15:21
• @AnjanKumar Yes, I understand that. But as I said, this is just an example. In reality this is part of a larger program, where I want to define a function g by something like g[n_] := f /@ li[n]. Mar 29, 2022 at 15:28
• Would g[n_Integer] := f /@ li[n] work? Mar 29, 2022 at 15:40
• You can use pattern matching as Roman commented, but you haven't specified what you want to happen for non-numeric inputs, so we can't provide a complete answer. Mar 29, 2022 at 16:07

You may use Composition.

With f as in OP

ClearAll[f]
f[{x_, y_}] := {x - y, x + y}


and lip some function that gives pairs

ClearAll[lip]
lip[n_] := Partition[Range[Abs@n, Abs@n + 9], 2]


In shorthand

ClearAll[g]
g[n_] := Map[f]@*lip@n


or longhand

ClearAll[h]
h[n_] := Composition[Map[f], lip][n]


Then both g and h give

g[n]

{{-1, 1 + 2 Abs[n]}, {-1, 5 + 2 Abs[n]}, {-1, 9 + 2 Abs[n]}, {-1, 13 + 2 Abs[n]}, {-1, 17 + 2 Abs[n]}}


and

g[n] /. n -> 5

{{-1, 11}, {-1, 15}, {-1, 19}, {-1, 23}, {-1, 27}}


Hope this helps.

• This only works because squaring is Listable. As I mentioned, this is not an option for me. Mar 29, 2022 at 21:30
• @NajibIdrissi Add a definition for f in OP that does not contain listable operations. Mar 29, 2022 at 21:37
• It's already there. Read through to the end. Mar 29, 2022 at 21:45
• @NajibIdrissi See update for f list to list. Mar 29, 2022 at 22:14

The function at the end of the OP cannot be applied to li[n] because, li[] does not return lists of length 2. It's rather confusing. Here's a way so that f will map itself over a list if the list contains lists, no matter what the "shape of the argument" is (e.g. length of the list, non-list, etc.).

li[n_] := Piecewise[{{Range[n, n + 10], n > 0}}, Range[-n, -n + 10]]

f // ClearAll;
f[v_?VectorQ] := Accumulate[v]; (* main definition *)
f[a_List] := f /@ a;            (* maps f *)
f[x_?NumericQ] := {x};          (* seemed to go with Accumulate *)


The li[] example (it doesn't make sense to map the second f):

g = f[li[x]]
(*
f[Piecewise[{
{{x, 1+x, 2+x, 3+x, 4+x, 5+x, 6+x, 7+x, 8+x, 9+x, 10+x}, x > 0}},
{-x, 1-x, 2-x, 3-x, 4-x, 5-x, 6-x, 7-x, 8-x, 9-x, 10-x}]]
*)

g /. x -> 3
(*  {3, 7, 12, 18, 25, 33, 42, 52, 63, 75, 88}  *)

g /. x -> -1
(*  {1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66}  *)


Here's a piecewise function in which f should be mapped in one case but not the other:

li2[n_] := Piecewise[
{{Table[Range[n, n + k], {k, 4}], n > 0}},
Range[-n, -n + 10]]

g2 = f[li2[x]];
g2 /. x -> 1
g2 /. x -> -1
(*
{{1, 3}, {1, 3, 6}, {1, 3, 6, 10}, {1, 3, 6, 10, 15}}
{1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66}
*)


Turning on tracing for f shows how f is evaluated on lists of lists:

On@f;
f[{1, {2, 3}, {4, 5, {6, 7, 8}}}]
(*
f::trace :  f[{1,{2,3},{4,5,{6,7,8}}}] --> f/@{1,{2,3},{4,5,{6,7,8}}}. >>
f::trace :  f[1] --> {1}. >>
f::trace :  f[{2,3}] --> Accumulate[{2,3}]. >>
f::trace :  f[{4,5,{6,7,8}}] --> f/@{4,5,{6,7,8}}. >>
f::trace :  f[4] --> {4}. >>
f::trace :  f[5] --> {5}. >>
f::trace :  f[{6,7,8}] --> Accumulate[{6,7,8}]. >>

{{1}, {2, 5}, {{4}, {5}, {6, 13, 21}}}
*)


Even if f is not defined to handle exactly the cases the OP has in mind, most likely a solution may be found to handle them by defining f with appropriate patterns.