8

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?

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
SShepard
  • 117
  • 3

7 Answers7

12

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.

Carl Woll
  • 130,679
  • 6
  • 243
  • 355
  • Will func stay in scope or not? It looks like it'll incur a memory leak as it has a reference to Map. – b3m2a1 Nov 01 '18 at 19:41
  • @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. – Carl Woll Nov 01 '18 at 19:47
  • 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. – b3m2a1 Nov 01 '18 at 19:49
7
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"}} *)
Dr. belisarius
  • 115,881
  • 13
  • 203
  • 453
2
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

Nasser
  • 143,286
  • 11
  • 154
  • 359
2

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.

kglr
  • 394,356
  • 18
  • 477
  • 896
  • 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. – Mr.Wizard Feb 05 '15 at 12:54
  • @MrWizard, please go ahead.. – kglr Feb 05 '15 at 12:56
  • 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. – Mr.Wizard Feb 05 '15 at 12:57
2

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 &]
ubpdqn
  • 60,617
  • 3
  • 59
  • 148
1
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"}}

eldo
  • 67,911
  • 5
  • 60
  • 168
1

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"}})

E. Chan-López
  • 23,117
  • 3
  • 21
  • 44