8
$\begingroup$

If we have a list like the following:

list1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};

And we use GatherBy to subpartition is according to a test function

GatherBy[list1, Mod[#, 3] &]

We have as an output for this example {{1, 4, 7, 10}, {2, 5, 8, 11}, {3, 6, 9, 12}}. See Artes' answer to my previous question: Subpartitioning elements of a list based on some h[ri] == h[rj] test

However, can we subpartition an unrelated list of the same length as list1, some list2 with arbitrary elements, in the same manner as list1 is subpartitioned via GatherBy?

For example, let's write some nonsense in list format:

list2 = {"It", "is", "only", "the", "morning", "the", "man", "complained", "I", "shall", "consider", "nothing"};

If we sort this list via the GatherBy output of the first list, we would have:

list2bylist1 = {{"It", "the", "man", "shall"}, {"is", "morning", "complained", "consider"}, {"only", "the", "I", "nothing"}};

Is there a way to do this simply?

$\endgroup$

7 Answers 7

7
$\begingroup$
list1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; 
list2 = {"It", "is", "only", "the", "morning", "the", "man", "complained", "I", "shall", 
         "consider", "nothing"}; 
GatherBy[Transpose[{list1, list2}], Mod[#[[1]], 3] &][[All, All, 2]]

(* {{"It", "the", "man", "shall"}, {"is", "morning", "complained",  "consider"}, 
    {"only", "the", "I", "nothing"}} *)
$\endgroup$
8
  • $\begingroup$ Beat me by 12 seconds. $\endgroup$
    – Szabolcs
    Commented Dec 6, 2014 at 19:42
  • 1
    $\begingroup$ @Szabolcs I made an answering bot using the new v10 natural lang features :) $\endgroup$ Commented Dec 6, 2014 at 19:43
  • $\begingroup$ @Szabolcs If I wish the test to be only that the elements at the indices are equal in list1 (without applying Mod 3), may I write: GatherBy[Transpose[{list1, list2}], #[[1]] &][[All, All, 2]]? $\endgroup$
    – SShepard
    Commented Dec 6, 2014 at 20:10
  • $\begingroup$ @SShepard Yes, That's it $\endgroup$ Commented Dec 6, 2014 at 20:12
  • $\begingroup$ @belisarius Thank you. $\endgroup$
    – SShepard
    Commented Dec 6, 2014 at 20:14
12
+50
$\begingroup$

Here is another approach. The basic idea is that GatherBy creates a list of representatives corresponding to the input, then partitions the input based on those representatives. We can see this in action with Trace. Here is an example:

func[x_] := Mod[x, 3]
Trace[GatherBy[{1, 2, 3, 4, 5}, func], TraceInternal->True]

{GatherBy[{1,2,3,4,5},func],{func/@{1,2,3,4,5},{func[1],func[2],func[3],func[4],func[5]},{func[1],Mod[1,3],1},{func[2],Mod[2,3],2},{func[3],Mod[3,3],0},{func[4],Mod[4,3],1},{func[5],Mod[5,3],2},{1,2,0,1,2}},{{1,4},{2,5},{3}}}

Notice how GatherBy internally computes func /@ {1,2,3,4,5} to create the list of representatives {1, 2, 0, 1, 2}. This list of representatives is then used to partition the input. So, to answer your question, we can override the Map so that it uses an alternate list of representatives. Here is a function that does this:

GatherByList[list_, representatives_] := Module[{func},
    func /: Map[func, _] := representatives;
    GatherBy[list, func]
]

For your example:

GatherByList[list2, Mod[list1, 3]]

{{"It", "the", "man", "shall"}, {"is", "morning", "complained", "consider"}, {"only", "the", "I", "nothing"}}

Let's compare timings with the accepted answer:

list1 = Range[10^6];
list2 = RandomReal[1, 10^6];

r1 = GatherByList[list2, Mod[list1, 3]]; //RepeatedTiming
r2 = GatherBy[Transpose[{list1, list2}], Mod[#[[1]], 3] &][[All, All, 2]]; //RepeatedTiming

r1 === r2

{0.013, Null}

{1.36, Null}

True

So, GatherByList is 2 orders of magnitude faster.

$\endgroup$
3
  • $\begingroup$ Will func stay in scope or not? It looks like it'll incur a memory leak as it has a reference to Map. $\endgroup$
    – b3m2a1
    Commented Nov 1, 2018 at 19:41
  • $\begingroup$ @b3m2a1 Did you check? I expect it will be garbage collected, and it does seem to be. Typically variables leave scope when they are included in the output, which is not the case here. $\endgroup$
    – Carl Woll
    Commented Nov 1, 2018 at 19:47
  • $\begingroup$ Nah, but when I used Module variables to attempt to construct a bound method it blew up in my face. On the other hand, I was returning one of those methods so the reference to some outer-scope function was kept. $\endgroup$
    – b3m2a1
    Commented Nov 1, 2018 at 19:49
2
$\begingroup$
list1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
idx = GatherBy[list1, Mod[#, 3] &]

list2 = {"It", "is", "only", "the", "morning", "the", "man", "complained", 
      "I", "shall",   "consider", "nothing"};

Part[list2, #] & /@ idx

Mathematica graphics

$\endgroup$
1
  • $\begingroup$ This only works because list1 is Range[12]. You need a variation if it is not. $\endgroup$
    – Mr.Wizard
    Commented Feb 5, 2015 at 12:50
2
$\begingroup$

Using a variant of Szabolcs's positionDuplicates:

shapeLikeF1[l1_List, l2_List, tstF_] :=
    Extract[l1, List /@ GatherBy[Range @ Length @ l2, tstF @ l2[[#]] &]]

shapeLikeF2[l1_List, l2_List, tstF_] :=
    l1[[#]] & /@ GatherBy[Range @ Length @ l2, tstF @ l2[[#]] &]

Examples:

list1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
list2 = {"It", "is", "only", "the", "morning", "the", "man", "complained", 
          "I", "shall", "consider", "nothing"};

shapeLikeF1[list2, list1, Mod[#, 3] &]

(* {{"It","the","man","shall"},{"is","morning","complained", "consider"},
{"only","the","I","nothing"}} *)

shapeLikeF1[list1, list2, StringLength]

(* {{1,2},{3},{4,6,7},{5,12},{8},{9},{10},{11}} *)

shapeLikeF2 produces the same output.

$\endgroup$
3
  • $\begingroup$ This uses Szabolcs's method and it is probably the fastest non-compiled way. (+1) Would you mind if I rewrite these using standard pattern definitions rather than With? I find the existing code unnecessarily hard to read. $\endgroup$
    – Mr.Wizard
    Commented Feb 5, 2015 at 12:54
  • $\begingroup$ @MrWizard, please go ahead.. $\endgroup$
    – kglr
    Commented Feb 5, 2015 at 12:56
  • $\begingroup$ Okay. I shall take the liberty of eliminating the duplicate examples as well; it should be sufficient to state that they produce the same output. $\endgroup$
    – Mr.Wizard
    Commented Feb 5, 2015 at 12:57
2
$\begingroup$

Just for something different:

list1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
list2 = {"It", "is", "only", "the", "morning", "the", "man", 
   "complained", "I", "shall", "consider", "nothing"};
Join @@ Last@
  Reap[MapThread[Sow[#2, Mod[#1, 3]] &, {list1, list2}], _, List@#2 &]
$\endgroup$
1
$\begingroup$
a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};

b = {"It", "is", "only", "the", "morning", "the", "man", "complained", "I", "shall", "consider", "nothing"};

c = GatherBy[a, Mod[#, 3] &]

{{1, 4, 7, 10}, {2, 5, 8, 11}, {3, 6, 9, 12}}

Using TakeList (new in 11.2):

TakeList[b, Length /@ c]

{{"It", "is", "only", "the"}, {"morning", "the", "man", "complained"}, {"I", "shall", "consider", "nothing"}}

Also:

Internal`CopyListStructure[c, b]

{{"It", "is", "only", "the"}, {"morning", "the", "man", "complained"}, {"I", "shall", "consider", "nothing"}}

$\endgroup$
1
$\begingroup$

Using GroupBy and Thread:

la = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};

lb = {"It", "is", "only", "the", "morning", "the", "man", 
      "complained", "I", "shall", "consider", "nothing"};

Values[GroupBy[Thread[la -> lb], Mod[#[[1]], 3] &]][[All, All, 2]]

(*{{"It", "the", "man", "shall"},
   {"is", "morning", "complained", "consider"},
   {"only", "the", "I", "nothing"}}*)
$\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.