36
$\begingroup$

I have three parallel lists (i.e. the elements in position i of each list are related). I want to sort the first list using the function Sort and make the same changes to the other lists so that I still have parallel lists when finished.

How can I do this?

As an example: Given the lists {2, 3, 1}, {a, b, c}, and {alpha, beta, gamma}, sorting all lists according the first one gives {1, 2, 3}, {c, a, b}, and {gamma, alpha, beta}.

$\endgroup$
  • $\begingroup$ I feel like this could be done by MMA 10's Association. Could anyone implement one? I set up the association: AssociationThread[{2, 3, 1} -> Transpose@{{a, b, c}, {alpha, beta, gamma}}], but not sure where to go from there. $\endgroup$ – seismatica Aug 12 '14 at 0:45
27
$\begingroup$
lists = {list1, list2, list3} = {{1, 3, 2}, {a, b, c}, {x, y, z}};

Another option

SortBy[lists\[Transpose], First]\[Transpose]

{{1, 2, 3}, {a, c, b}, {x, z, y}}

$\endgroup$
  • $\begingroup$ Why is \[Transpose] necessary? $\endgroup$ – Tyson Williams Jun 29 '12 at 20:35
  • $\begingroup$ @TysonWilliams \[Transpose] is a nice notation (see it in the front end) for the matrix Transpose function. You first create a single list such that it's i'th element is {list1[[i]], list2[[i]], list3[[i]]}. Then, you sort that list just by looking at its elemtents' first elements $\endgroup$ – Rojo Jun 29 '12 at 20:37
  • $\begingroup$ In other words, you start off with a matrix in which each ROW its one of your lists (thats lists), and you transpose it, and now your lists are the columns. Now you sort the rows of that matrix just by looking at the frist column, and transpose again $\endgroup$ – Rojo Jun 29 '12 at 20:38
  • $\begingroup$ Transpose is usually a very efficient operation $\endgroup$ – Rojo Jun 29 '12 at 20:39
  • 1
    $\begingroup$ I accepted this solution since it is inline. $\endgroup$ – Tyson Williams Jun 29 '12 at 20:56
33
$\begingroup$

You can use combination of Part and Ordering as

list1[[ Ordering @ list2 ]]

to sort list1 in the order of list2.

Examples:

{list1, list2, list3} = {{1, 3, 2}, {a, b, c}, {x, y, z}};
list2[[ Ordering @ list1 ]]

gives

{a, c, b}

and

list3[[ Ordering @ list1 ]]

gives

{x, z, y}

EDIT: Using with lists of lists, to sort the entire array based on the first list:

list = {list1, list2, list3};
list[[ All, Ordering @ list[[1]] ]]

gives

{{1, 2, 3}, {a, c, b}, {x, z, y}}

But ... as I just noticed, this is already covered in @Mr.Wizard's answer long before my edit.

$\endgroup$
  • $\begingroup$ Very good. Thanks for the solution! $\endgroup$ – Tyson Williams Jun 29 '12 at 20:29
  • $\begingroup$ @TysonWilliams, my pleasure.. $\endgroup$ – kglr Jun 29 '12 at 20:31
23
$\begingroup$

Ordering and Part is more efficient than SortBy and Transpose and it can also be done in one pass as I will demonstrate.

I create three lists of different type as described in the question:

a = RandomInteger[999, 500];
b = RandomReal[1, 500];
c = CharacterRange["a", "z"] ~RandomChoice~ 500;

I use the timeAvg function for testing:

SortBy[{a, b, c}\[Transpose], First]\[Transpose] // timeAvg

{a, b, c}[[All, Ordering@a]] // timeAvg

0.00027456

0.000026944

As can be seen second method is more than an order of magnitude faster on this data.

It is noteworthy that these two forms as shown do not perform the same operation because SortBy[list, func] is not a stable sort. Observe:

lists = {{8, 8, 6, 3, 7},
         {"i", "e", "f", "b", "m"},
         {"q", "x", "u", "w", "z"}};

SortBy[lists\[Transpose], First]\[Transpose]

lists[[All, Ordering @ First @ lists]]
{{3, 6, 7, 8, 8}, {"b", "f", "m", "e", "i"}, {"w", "u", "z", "x", "q"}}

{{3, 6, 7, 8, 8}, {"b", "f", "m", "i", "e"}, {"w", "u", "z", "q", "x"}}

You can see see that SortBy has swapped the positions of "i"/"e" and "q"/"x" in the lists so it is not a minimal reordering. This can be corrected however with a different syntax for SortBy:

SortBy[lists\[Transpose], {First}]\[Transpose]
{{3, 6, 7, 8, 8}, {"b", "f", "m", "i", "e"}, {"w", "u", "z", "q", "x"}}

This syntax also speeds up SortBy, but not enough to be competitive with Ordering:

SortBy[{a, b, c}\[Transpose], {First}]\[Transpose] // timeAvg

0.0001248

$\endgroup$
5
$\begingroup$

This is a more general solution that I came up with some time ago to solve some rearrangement problems. I'm pretty sure that it is slower than any of the solutions above (as it uses ReplacePart), but it is general enough to deal with any kind of ragged partitions. Usage: partitionAs sorts and partitions a list just as a reference list ref is ordered and partitioned.

partitionAs[list_, ref_] := Module[{ord = Ordering@Flatten@ref, 
    pos = Position[ref, Except[_List | List]]}, 
   ReplacePart[ref, Thread[pos -> Flatten[list][[ord]]]]];

ref = {{2, {9}, 0}, {3, 7}, 6, {4, {8, 5}, 1}}
list = {"A", "B", {"C", "D"}, "E", {"F", "G"}, "H", "I", "J"};

partitionAs[list, ref]

{{2, {9}, 0}, {3, 7}, 6, {4, {8, 5}, 1}}

{{"C", {"J"}, "A"}, {"D", "G"}, "I", {"F", {"E", "H"}, "B"}}

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