10

I have the two lists in Python

list_1 = [5, 2, 8];
list_2 = ['string1', 'string2', 'string3']

I would like to sort the fist list and use the result to sort the second list.

In other words, the result should be:

# Sorted in descending order
list_1_sorted = [8, 5, 2];
list_2_sorted = ['string3', 'string1', 'string2'];

I know how to sort each of these lists individually, but how I can permute one list using the permutation of indices resulting from sorting the other list?

Francis Avila
  • 31,233
  • 6
  • 58
  • 96
Amelio Vazquez-Reina
  • 91,494
  • 132
  • 359
  • 564
  • 2
    It's still not clear what you mean by "use the result of other". The example doesn't illustrate why you don't want to just sort both lists independently. – prelic Mar 03 '12 at 03:41
  • 3
    @prelic: He wants to sort `list_1`, and use the same permutation to order `list_2`. – Amadan Mar 03 '12 at 03:43

4 Answers4

20

Schwartzian transform

list_1_sorted, list_2_sorted = zip(*sorted(zip(list_1, list_2),
  key=operator.itemgetter(0), reverse=True))
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • 1
    Ugh, I wanted to figure out how to use unpacking to make this a one liner, but wasn't quite quick witted enough. +1 for that and "Schwartzian transform". – Wilduck Mar 03 '12 at 03:50
  • 1
    The `key` is unnecessary here since that's already the natural sort order for tuples. Unless you deliberately want to make the sort unstable in the case that `list_1` contains duplicates... – Karl Knechtel Mar 03 '12 at 13:09
15

Zip the lists together, sort, unzip the lists:

together = zip(list_1, list_2)
sorted_together =  sorted(together)

list_1_sorted = [x[0] for x in sorted_together]
list_2_sorted = [x[1] for x in sorted_together]

What's happening here is that zip creates a list of tuples, with the elements you want the list to be sorted by being the first elements:

>>> a = [1,3,7,3,2]
>>> b = ["one","two",'three','four','five']
>>> zip(a,b)
[(1, 'one'), (3, 'two'), (7, 'three'), (3, 'four'), (2, 'five')]

Then when you sort them, they elements stay paired:

>>> sorted(zip(a,b))
[(1, 'one'), (2, 'five'), (3, 'four'), (3, 'two'), (7, 'three')]

Then all that's left is to unpack these lists.

Wilduck
  • 13,822
  • 10
  • 58
  • 90
3

You can use zip:

>>> list_1 = ['string1', 'string2', 'string3']
>>> list_2 = [5, 2, 8]
>>> s = sorted(zip(list_2, list_1), reverse=True)
>>> list_1_sorted = [e[1] for e in s]
>>> list_2_sorted = [e[0] for e in s]
>>> list_1_sorted
['string3', 'string1', 'string2']
>>> list_2_sorted
[8, 5, 2]
>>> 
icyrock.com
  • 27,952
  • 4
  • 66
  • 85
2

@Ignacio's answer is the best, but just in case you need to sort the lists in-place without making new lists, you can try this:

import itertools
list_enumerate = itertools.count()

list_2.sort(reverse=True, key=lambda k: list_1[next(list_enumerate)])
list_1.sort(reverse=True)
print list_1
print list_2

Note that I do not think there is any guarantee that the key function is called for each list item in order (which is necessary for this to work), so this is a risky method to use.

Francis Avila
  • 31,233
  • 6
  • 58
  • 96
  • There is no such thing as a "requirement to operate in-place" in Python. You can always just assign back, and if you're worried about optimization on a memory-layout level, you're using the wrong language anyway. – Karl Knechtel Mar 03 '12 at 13:11
  • The [dodumentation](http://wiki.python.org/moin/HowTo/Sorting/#Key_Functions) says the key function is "called on each list element prior to making comparisons" and "exactly once for each input record". I'd take this as almost a guarantee they are also called in the correct order. – Sven Marnach Mar 03 '12 at 14:35