5
$\begingroup$

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]} *)
$\endgroup$
4
  • $\begingroup$ Its the execution order which needs to be modified. You can enclose li[n] replacement with brackets like this: f /@ (li[n] /. n -> 5). $\endgroup$ Mar 29, 2022 at 15:21
  • $\begingroup$ @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]. $\endgroup$ Mar 29, 2022 at 15:28
  • 1
    $\begingroup$ Would g[n_Integer] := f /@ li[n] work? $\endgroup$
    – Roman
    Mar 29, 2022 at 15:40
  • $\begingroup$ 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. $\endgroup$
    – lericr
    Mar 29, 2022 at 16:07

2 Answers 2

5
$\begingroup$

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.

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

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.

$\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.