5
$\begingroup$

Preliminaries

I have lists that are lists of lists (i.e., each list contains one or more sublists and are ragged (i.e., each sublist can have a different length, though all have a length of 1 or greater).  However, each list is similarly ragged -- each list has the same number of sublists, and corresponding sublists have the same length.

As an example, consider the lists listA, listB, and listC:

listA = {{A1x}, {A2x, A2y, A2z}, {A3x, A3y}, {A4x, A4y, A4z, A4a}};
listB = {{B1x}, {B2x, B2y, B2z}, {B3x, B3y}, {B4x, B4y, B4z, B4a}};
listC = {{C1x}, {C2x, C2y, C2z}, {C3x, C3y}, {C4x, C4y, C4z, C4a}};

Objective

I wish to write a function myFunction that maps a function func across corresponding elements of two or more lists.  myFunction would have the form

myFunction[func, lists__]

I wish to define myFunction such that the function call

myFunction[f, listA, listB] (* function call 1 *)

returns this output:

(* function call 1 desired output *)
{
 {f[{A1x, B1x}]},
 {f[{A2x, B2x}], f[{A2y, B2y}], f[{A2z, B2z}]},
 {f[{A3x, B3x}], f[{A3y, B3y}]},
 {f[{A4x, B4x}], f[{A4y, B4y}], f[{A4z, B4z}], f[{A4a, B4a}]}
}

while the function call

myFunction[f, listA, listB, listC] (* function call 2 *)

returns this output:

(* function call 2 desired output *)
{
 {f[{A1x, B1x, C1x}]},
 {f[{A2x, B2x, C2x}], f[{A2y, B2y, C2y}], f[{A2z, B2z, C2z}]},
 {f[{A3x, B3x, C3x}], f[{A3y, B3y, C3y}]},
 {f[{A4x, B4x, C4x}], f[{A4y, B4y, C4y}], f[{A4z, B4z, C4z}], f[{A4a, B4a, C4a}]}
}

where f can be the built-in function Mean to compute a simple average.

Additionally, I would like myFunction to be compatible with Mathematica 9 and later, so myFunction needs to use functions available in Mathematica 9 or earlier.

Could you please help me think through how to accomplish this? (I am not a student and this is not a homework assignment.)

Attempts at reaching the objective

I've started by considering the first function call, myFunction[f, listA, listB], which involves only two lists.  Combining Map and Transpose gets me seemingly somewhat close to my goal:

myFunction[func_, list1_List, list2_List] := Map[func, Transpose[{listA, listB}];

myFunction[f, listA, listB]
(* output *)
{
 f[{{A1x}, {B1x}}], 
 f[{{A2x, A2y, A2z}, {B2x, B2y, B2z}}], 
 f[{{A3x, A3y}, {B3x, B3y}}], 
 f[{{A4x, A4y, A4z, A4a}, {B4x, B4y, B4z, B4a}}]
}

... except that I would need f to be mapped deeper for sublists with length greater than 1. One (not very elegant) way to do this may be:

myFunction[func_, list1_List, list2_List] := Map[
 If[
   Length[#[[1]]] == 1, {func[Flatten[#]]},
   Map[func, Transpose[#]]
   ] &,
 Transpose[{listA, listB}]];

myFunction[f, listA, listB]
(* output *)
{
 {f[{A1x, B1x}]}, 
 {f[{A2x, B2x}], f[{A2y, B2y}], f[{A2z, B2z}]}, 
 {f[{A3x, B3x}], f[{A3y, B3y}]}, 
 {f[{A4x, B4x}], f[{A4y, B4y}], f[{A4z, B4z}], f[{A4a, B4a}]}
}

I imagine there's a much more elegant, compact way to do that, but this appears to work. But even if I use this definition, I still need to generalize it to 3 or more input lists.

How do I do that? I think I need to use the BlankSequence[] pattern object (two _ characters) for the lists input:

myFunction[func_, lists__] := Map[
  If[
    Length[#[[1]]] == 1, {func[Flatten[#]]},
    Map[func, Transpose[#]]
    ] &,
  Transpose[{lists}]];

myFunction[f, listA, listB, listC]
(* output *)
{
 {f[{A1x,B1x,C1x}]},
 {f[{A2x,B2x,C2x}],f[{A2y,B2y,C2y}],f[{A2z,B2z,C2z}]},
 {f[{A3x,B3x,C3x}],f[{A3y,B3y,C3y}]},
 {f[{A4x,B4x,C4x}],f[{A4y,B4y,C4y}],f[{A4z,B4z,C4z}],f[{A4a,B4a,C4a}]}
}

And this is the result I was looking for. Is there a more compact way to do this? MapThread looks possibly useful, but it's not clear to me how to use for my particular application.

$\endgroup$
6
  • 2
    $\begingroup$ like that? : Function[, f[{##}], Listable][listA, listB] $\endgroup$
    – Kuba
    Commented Jul 6, 2022 at 21:00
  • 1
    $\begingroup$ @Kuba : Function[, f[##] ... , not Function[, f[{##}] ... $\endgroup$
    – andre314
    Commented Jul 6, 2022 at 21:01
  • 1
    $\begingroup$ Kuba's solution is probably what you need, except if what you describe here as atoms (A1X, A2X etc...) are in fact themselves Lists ( {} ) $\endgroup$
    – andre314
    Commented Jul 6, 2022 at 21:09
  • 2
    $\begingroup$ How about MapThread[MapThread[f@*List]@*List, {listA, listB, listC}]? $\endgroup$
    – Carl Woll
    Commented Jul 6, 2022 at 21:11
  • 1
    $\begingroup$ @andre314 but OP asked for f[{A1x, B1x}] not f[A1x, B1x] and for the latter case just making f Listable will do the job. $\endgroup$
    – Kuba
    Commented Jul 6, 2022 at 21:15

3 Answers 3

7
$\begingroup$
MapThread[f@*List] /@ Transpose[lists]
$\endgroup$
5
$\begingroup$

I'm just putting this here to have an actual answer instead of just comments. The key insight here is that the Listable attribute provides this functionality nicely. Here are two possibilities that match your method signature, but there are other options, perhaps nicer for some circumstances, if you relaxed that requirement a bit:

(* option 1*)
SetAttributes[myFunction1, Listable];
myFunction1[f_, args__] := f[{args}]

(* option 2*)
myFunction2 = Function[, #1[{##2}], Listable]
$\endgroup$
2
$\begingroup$

By using Thread we would get:

Thread /@ Thread[f[listA, listB]]

(* Out: {{f[A1x, B1x]}, {f[A2x, B2x], f[A2y, B2y], f[A2z, B2z]}, {f[A3x, B3x], f[A3y, B3y]}, {f[A4x, B4x], f[A4y, B4y], f[A4z, B4z], f[A4a, B4a]}} *)

For replacing f[a1,a2] to f[{a1, a2}] we can use:

1. By Hold

ReleaseHold[Thread /@ Thread[Hold[f@*List][listA, listB]]]

(* Out: {{f[{A1x, B1x}]}, {f[{A2x, B2x}], f[{A2y, B2y}], f[{A2z, B2z}]}, {f[{A3x, B3x}], f[{A3y, B3y}]}, {f[{A4x, B4x}], f[{A4y, B4y}], f[{A4z, B4z}], f[{A4a, B4a}]}} *)

2. By Apply

Apply[f@*List, Thread /@ Thread@{listA, listB}, {2}]

(* Out: {{f[{A1x, B1x}]}, {f[{A2x, B2x}], f[{A2y, B2y}], f[{A2z, B2z}]}, {f[{A3x, B3x}], f[{A3y, B3y}]}, {f[{A4x, B4x}], f[{A4y, B4y}], f[{A4z, B4z}], f[{A4a, B4a}]}} *)

The above solutions (especially second) are slightly faster than @Alan, but use more memory!

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