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
$\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$
  • $\begingroup$ Beat me by 12 seconds. $\endgroup$ – Szabolcs Dec 6 '14 at 19:42
  • 1
    $\begingroup$ @Szabolcs I made an answering bot using the new v10 natural lang features :) $\endgroup$ – Dr. belisarius Dec 6 '14 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 Dec 6 '14 at 20:10
  • $\begingroup$ @SShepard Yes, That's it $\endgroup$ – Dr. belisarius Dec 6 '14 at 20:12
  • $\begingroup$ @belisarius Thank you. $\endgroup$ – SShepard Dec 6 '14 at 20:14
11
+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$
  • $\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 Nov 1 '18 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 Nov 1 '18 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 Nov 1 '18 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$
  • $\begingroup$ This only works because list1 is Range[12]. You need a variation if it is not. $\endgroup$ – Mr.Wizard Feb 5 '15 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$
  • $\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 Feb 5 '15 at 12:54
  • $\begingroup$ @MrWizard, please go ahead.. $\endgroup$ – kglr Feb 5 '15 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 Feb 5 '15 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$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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