PedsB
PedsB

Reputation: 311

Sort third list using sorting resulting from two list sort?

Say I have three lists:

X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,    0,   1,   2,   2,   0,   1]
Z = [ 5,   2,   3,    1,   4,   9,   6,   0,   7]

I can sort X according to Y rather easily:

XY_sort = [x for _,x in sorted(zip(Y,X))]
print(XY_sort)  # ["a", "d", "h", "b", "c", "e", "i", "f", "g"]

Doubts arise when I want to sort list Z according to the sorting that produced XY_sort. Ideally, I want to end up with:

Z_sort = [5,   1,   0,   2,   3,   4,   7,   9,   6]

I'm guessing the best way to do this would be to somehow store the sorted indices of X when it gets sorted into XY_sort, then using those to sort Z, but I don't know how I would go about doing this. Any help would be greatly appreciated!

Upvotes: 2

Views: 91

Answers (3)

Mark
Mark

Reputation: 92460

You can sort the same way. It will sort first by Y, then X, if these are both the same it will sort by Z:

X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,    0,   1,   2,   2,   0,   1]
Z = [ 5,   2,   3,    1,   4,   9,   6,   0,   7]


XYZ_sort = [z for y,x,z in sorted(zip(Y,X,Z))]
# [5, 1, 0, 2, 3, 4, 7, 9, 6]

[Edit thanks to the commenter's tenacity]
If it's possible that X-Y combinations are not unique, and if it's important that you preserve the relative order of Z in those cases, you can pass operator.itemgetter() as the key to the sort for a clean solution:

from operator import itemgetter

X = ["a", "b", "c", "a", "d", "e", "f", "g", "h", "i", "a"]
Y = [ 0,   1,   1,   0,  0,   1,   2,   2,   0,   1,  0]
Z = [ 5,   2,   3,  -1,  1,   4,   9,   6,   0,   7,  1]

XYZ_sort = [z for y,x,z in sorted(zip(Y,X,Z), key=itemgetter(0, 1))]

#preseves the relative order of 5, -1, and 1
# [5, -1, 1, 1, 0, 2, 3, 4, 7, 9, 6]

Upvotes: 6

dawg
dawg

Reputation: 104032

You can use a key argument to call out the elements of the tuple that zip produces to sort as you desire:

>>> sorted(zip(X,Y,Z), key=lambda t: (t[1], t[0]))
[('a', 0, 5), ('d', 0, 1), ('h', 0, 0), ... ('f', 2, 9), ('g', 2, 6)]

Then filter out X and Y in the resulting tuple:

>>> [z for x,y,z in sorted(zip(X,Y,Z), key=lambda t: (t[1], t[0]))]
[5, 1, 0, 2, 3, 4, 7, 9, 6]

The advantage here is that Z is not considered in the sort order at all; only the two elements in the key function that match X and Y are used.


BTW: I usually answer all Python questions with Python 3 syntax. However, Python 2 had a really great syntax for lambda functions that would automatically unpack a tuple passed as an argument.

Python 2 only example:

>>> sorted(zip(X,Y,Z), key=lambda (x,y,z): (y, x))
[('a', 0, 5), ('d', 0, 1), ... ('f', 2, 9), ('g', 2, 6)]
>>> [z for x,y,z in sorted(zip(X,Y,Z), key=lambda (x,y,z): (y, x))]
[5, 1, 0, 2, 3, 4, 7, 9, 6]

That syntax has been retired unfortunately....


As stated in comments, this works too:

>>> [z for z,y,x in sorted(zip(Z,Y,X), key=lambda t: t[1:])]
[5, 1, 0, 2, 3, 4, 7, 9, 6]

Upvotes: 2

superb rain
superb rain

Reputation: 5521

You say "sort X according to Y", but then you really sort X according to Y and X. If you truly sorted it only by Y, you could use the same for Z as well.

Upvotes: -1

Related Questions