233

Suppose I have:

list1 = [3, 2, 4, 1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

Calling list1.sort() will sort it, resulting in [1, 1, 2, 3, 4]. However, can I get list2 to be rearranged in sync with that, to get a result like this?

list1 = [1, 1, 2, 3, 4]
list2 = ['one', 'one2', 'two', 'three', 'four']

Sometimes, people phrase the problem differently: given two lists, they would like to use one to determine the sort order for the other - i.e., sort list2 in the order described by the corresponding values in list1. The trick is that this is equivalent to sorting the "key" values (list1), and then rearranging list2 in the same way. In other words, exactly what is described here. Some answers for the other question, though, discard the "sorted keys" afterwards.

See also: How can I sort a list, according to where its elements appear in another list? - this is another common way that people want to sort one list "based on" another. Before attempting to close duplicate questions, take special care to check exactly what the OP wants. A key clue: do the lists need to be the same length?

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Lostsoul
  • 25,013
  • 48
  • 144
  • 239
  • I should point out that your variables in list2 don't point to the ints in list1. E.g. if change a value such as list1[0]=9 and look at list2, list2[0] will still be 3. With integers in python, it doesn't use the reference/pointer, it copies the value. You would have been better off going list2 = list1[:] – Rusty Rob Mar 19 '12 at 03:10

16 Answers16

369

One classic approach to this problem is to use the "decorate, sort, undecorate" idiom, which is especially simple using python's built-in zip function:

>>> list1 = [3,2,4,1, 1]
>>> list2 = ['three', 'two', 'four', 'one', 'one2']
>>> list1, list2 = zip(*sorted(zip(list1, list2)))
>>> list1
(1, 1, 2, 3, 4)
>>> list2 
('one', 'one2', 'two', 'three', 'four')

These of course are no longer lists, but that's easily remedied, if it matters:

>>> list1, list2 = (list(t) for t in zip(*sorted(zip(list1, list2))))
>>> list1
[1, 1, 2, 3, 4]
>>> list2
['one', 'one2', 'two', 'three', 'four']

It's worth noting that the above may sacrifice speed for terseness; the in-place version, which takes up 3 lines, is a tad faster on my machine for small lists:

>>> %timeit zip(*sorted(zip(list1, list2)))
100000 loops, best of 3: 3.3 us per loop
>>> %timeit tups = zip(list1, list2); tups.sort(); zip(*tups)
100000 loops, best of 3: 2.84 us per loop

On the other hand, for larger lists, the one-line version could be faster:

>>> %timeit zip(*sorted(zip(list1, list2)))
100 loops, best of 3: 8.09 ms per loop
>>> %timeit tups = zip(list1, list2); tups.sort(); zip(*tups)
100 loops, best of 3: 8.51 ms per loop

As Quantum7 points out, JSF's suggestion is a bit faster still, but it will probably only ever be a little bit faster, because Python uses the very same DSU idiom internally for all key-based sorts. It's just happening a little closer to the bare metal. (This shows just how well optimized the zip routines are!)

I think the zip-based approach is more flexible and is a little more readable, so I prefer it.


Note that when elements of list1 are equal, this approach will end up comparing elements of list2. If elements of list2 don't support comparison, or don't produce a boolean when compared (for example, if list2 is a list of NumPy arrays), this will fail, and if elements of list2 are very expensive to compare, it might be better to avoid comparison anyway.

In that case, you can sort indices as suggested in jfs's answer, or you can give the sort a key function that avoids comparing elements of list2:

result1, result2 = zip(*sorted(zip(list1, list2), key=lambda x: x[0]))

Also, the use of zip(*...) as a transpose fails when the input is empty. If your inputs might be empty, you will have to handle that case separately.

user2357112
  • 260,549
  • 28
  • 431
  • 505
senderle
  • 145,869
  • 36
  • 209
  • 233
  • 7
    what does the asterisk in the third line represent? – Jeffrey Mar 19 '12 at 05:25
  • 10
    To elaborate on the above, the `*` operator does [argument unpacking](http://docs.python.org/tutorial/controlflow.html#unpacking-argument-lists), – senderle Mar 19 '12 at 15:46
  • 1
    The sorted index/map paradigm suggested by J.F. Sebastian is about 10% faster than either zip solution for me (using lists of 10000 random ints): %timeit index = range(len(l1)); index.sort(key=l1.__getitem__); map(l1.__getitem__, index); map(l2.__getitem__, index) 100 loops, best of 3: 8.04 ms per loop (vs 9.17 ms, 9.07 ms for senderle's timits) – Quantum7 Aug 12 '13 at 21:35
  • 2
    The first and second zip in list1, list2 = zip(*sorted(zip(list1, list2))) do such different things. The * makes all the difference. – piedpiper Jan 13 '18 at 09:48
  • 2
    @ashu, in a sense, yes! But in another sense, they're hardly different at all. `zip(*x)` has the interesting property that it is its own inverse: `l = [(1, 2), (3, 4)]; list(zip(*zip(*l))) == l` returns `True`. It's effectively a transposition operator. `zip()` on its own is just the same operator, but assumes that you have unpacked the input sequence manually. – senderle Jan 16 '18 at 14:43
  • 1
    Yes. I wonder if the subtlety was only something I stumbled at and is obvious to everyone, or is indeed a little involved. Noticing this property of zip to make this entire operation work in one line is non trivial at the very least. – piedpiper Jan 16 '18 at 17:52
  • 1
    @senderie You can get rid of the tuple comprehension in your second code paragraph and use `list1, list2 = map(list, zip(*sorted(zip(list1, list2))))`. – Guimoute Apr 30 '21 at 20:00
  • @Guimoute, ah well it's a matter of opinion I guess—but I generally find generator expressions and list comprehensions more readable than `map`. – senderle Apr 30 '21 at 20:49
  • which is the one line version? I don't get it... what is the version to get a list and so it would be fast on big sets? – Flash Thunder Jan 25 '22 at 20:24
49

You can sort indexes using values as keys:

indexes = range(len(list1))
indexes.sort(key=list1.__getitem__)

To get sorted lists given sorted indexes:

sorted_list1 = map(list1.__getitem__, indexes)
sorted_list2 = map(list2.__getitem__, indexes)

In your case you shouldn't have list1, list2 but rather a single list of pairs:

data = [(3, 'three'), (2, 'two'), (4, 'four'), (1, 'one'), (1, 'one2')]

It is easy to create; it is easy to sort in Python:

data.sort() # sort using a pair as a key

Sort by the first value only:

data.sort(key=lambda pair: pair[0])
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • 1
    The cool thing about this is that I can keep indexes around and sort other stuff later, in case list1 is an important coordinate that affects several other arrays. – EL_DON Mar 05 '18 at 18:35
  • 8
    indexes = list(range(len(list1))) for python 3 – DonQuiKong Nov 02 '18 at 09:38
  • 1
    @DonQuiKong you also need to `list() `around `map()` if you'd like to use this code in Python 3. – jfs Nov 02 '18 at 16:51
  • 2
    Or, instead of `sorted_list1 = list(map(list1.__getitem__, indexes))` one could do `sorted_list1 = [list1[i] for i in indexes]`. – Nathan Jun 21 '20 at 03:57
  • You know it is obvious once you see it but my brain certainly wasn't coming up with this. – Mark Rucker May 04 '23 at 02:56
32

I have used the answer given by senderle for a long time until I discovered np.argsort. Here is how it works.

# idx works on np.array and not lists.
list1 = np.array([3,2,4,1])
list2 = np.array(["three","two","four","one"])
idx   = np.argsort(list1)

list1 = np.array(list1)[idx]
list2 = np.array(list2)[idx]

I find this solution more intuitive, and it works really well. The perfomance:

def sorting(l1, l2):
    # l1 and l2 has to be numpy arrays
    idx = np.argsort(l1)
    return l1[idx], l2[idx]

# list1 and list2 are np.arrays here...
%timeit sorting(list1, list2)
100000 loops, best of 3: 3.53 us per loop

# This works best when the lists are NOT np.array
%timeit zip(*sorted(zip(list1, list2)))
100000 loops, best of 3: 2.41 us per loop

# 0.01us better for np.array (I think this is negligible)
%timeit tups = zip(list1, list2); tups.sort(); zip(*tups)
100000 loops, best for 3 loops: 1.96 us per loop

Even though np.argsort isn't the fastest one, I find it easier to use.

  • 1
    I get an error running your example: `TypeError: only integer arrays with one element can be converted to an index` (Python 2.7.6, numpy 1.8.2). To fix it, list1 and list2 must be declared as numpy arrays. – BenB Jul 07 '15 at 00:53
  • Thanks. Isn't this what I write in the comment in the function? Anyway, I think it's silly that `np.argsort` don't try to convert to a `np.array` internally. – Daniel Thaagaard Andreasen Jul 07 '15 at 12:37
  • I was referring to the first code snippet since it doesn't run as written :) – BenB Jul 07 '15 at 20:50
  • I corrected it by converting the lists when they are assigned to numpy arrays. Thanks for the comment :) – Daniel Thaagaard Andreasen Jul 08 '15 at 14:47
  • Now they're converted to Numpy arrays twice ;) – BenB Jul 08 '15 at 19:32
  • I call it bullet proof, you may call it something else ;) Yes, it is only needed to make the lists numpy arrays once (in the beginning preferrably, then you don't worry about it anymore). – Daniel Thaagaard Andreasen Jul 09 '15 at 01:04
16

This can be done using what Perl programmers call the Schwartzian transform, also known as the decorate-sort-undecorate idiom. The built-in Python sorting is stable, so the two 1s don't cause a problem.

>>> l1 = [3, 2, 4, 1, 1]
>>> l2 = ['three', 'two', 'four', 'one', 'second one']
>>> zip(*sorted(zip(l1, l2)))
[(1, 1, 2, 3, 4), ('one', 'second one', 'two', 'three', 'four')]
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
  • 3
    However, if you find you need to do this, you should strongly re-consider having the two "parallel" lists of data, as opposed to keeping a list of 2-tuples (pairs)... or perhaps even actually creating a class. – Karl Knechtel Mar 19 '12 at 02:47
5

You can use the zip() and sort() functions to accomplish this:

Python 2.6.5 (r265:79063, Jun 12 2010, 17:07:01)
[GCC 4.3.4 20090804 (release) 1] on cygwin
>>> list1 = [3,2,4,1,1]
>>> list2 = ['three', 'two', 'four', 'one', 'one2']
>>> zipped = zip(list1, list2)
>>> zipped.sort()
>>> slist1 = [i for (i, s) in zipped]
>>> slist1
[1, 1, 2, 3, 4]
>>> slist2 = [s for (i, s) in zipped]
>>> slist2
['one', 'one2', 'two', 'three', 'four']

Hope this helps

Hunter McMillen
  • 59,865
  • 24
  • 119
  • 170
  • 1
    Is anyone else getting the error "AttributeError: 'zip' object has no attribute 'sort'"? I'm wondering if this response works for earlier versions of Python but not current ones. – Non-Contradiction Oct 25 '21 at 18:32
5

One way is to track where each index goes to by sorting the identity [0,1,2,..n]

This works for any number of lists.

Then move each item to its position. Using splices is best.

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

index = list(range(len(list1)))
print(index)
'[0, 1, 2, 3, 4]'

index.sort(key = list1.__getitem__)
print(index)
'[3, 4, 1, 0, 2]'

list1[:] = [list1[i] for i in index]
list2[:] = [list2[i] for i in index]

print(list1)
print(list2)
'[1, 1, 2, 3, 4]'
"['one', 'one2', 'two', 'three', 'four']"

Note we could have iterated the lists without even sorting them:

list1_iter = (list1[i] for i in index)
Rusty Rob
  • 16,489
  • 8
  • 100
  • 116
4

If you are using numpy you can use np.argsort to get the sorted indices and apply those indices to the list. This works for any number of list that you would want to sort.

import numpy as np

arr1 = np.array([4,3,1,32,21])
arr2 = arr1 * 10
sorted_idxs = np.argsort(arr1)

print(sorted_idxs)
>>> array([2, 1, 0, 4, 3])

print(arr1[sorted_idxs])
>>> array([ 1,  3,  4, 21, 32])

print(arr2[sorted_idxs])
>>> array([ 10,  30,  40, 210, 320])
Kurtis Streutker
  • 1,157
  • 10
  • 13
4

What about:

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

sortedRes = sorted(zip(list1, list2), key=lambda x: x[0]) # use 0 or 1 depending on what you want to sort
>>> [(1, 'one'), (1, 'one2'), (2, 'two'), (3, 'three'), (4, 'four')]
Artsiom Rudzenka
  • 27,895
  • 4
  • 34
  • 52
2

You can use the key argument in sorted() method unless you have two same values in list2.

The code is given below:

sorted(list2, key = lambda x: list1[list2.index(x)]) 

It sorts list2 according to corresponding values in list1, but make sure that while using this, no two values in list2 evaluate to be equal because list.index() function give the first value

Saurav Yadav
  • 115
  • 9
1

Another approach to retaining the order of a string list when sorting against another list is as follows:

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

# sort on list1 while retaining order of string list
sorted_list1 = [y for _,y in sorted(zip(list1,list2),key=lambda x: x[0])]
sorted_list2 = sorted(list1)

print(sorted_list1)
print(sorted_list2)

output

['one', 'one2', 'two', 'three', 'four']
[1, 1, 2, 3, 4]
brock
  • 11
  • 1
  • 1
1

I would like to expand open jfs's answer, which worked great for my problem: sorting two lists by a third, decorated list:

We can create our decorated list in any way, but in this case we will create it from the elements of one of the two original lists, that we want to sort:

# say we have the following list and we want to sort both by the algorithms name 
# (if we were to sort by the string_list, it would sort by the numerical 
# value in the strings)
string_list = ["0.123 Algo. XYZ", "0.345 Algo. BCD", "0.987 Algo. ABC"]
dict_list = [{"dict_xyz": "XYZ"}, {"dict_bcd": "BCD"}, {"dict_abc": "ABC"}]

# thus we need to create the decorator list, which we can now use to sort
decorated = [text[6:] for text in string_list]  
# decorated list to sort
>>> decorated
['Algo. XYZ', 'Algo. BCD', 'Algo. ABC']

Now we can apply jfs's solution to sort our two lists by the third

# create and sort the list of indices
sorted_indices = list(range(len(string_list)))
sorted_indices.sort(key=decorated.__getitem__)

# map sorted indices to the two, original lists
sorted_stringList = list(map(string_list.__getitem__, sorted_indices))
sorted_dictList = list(map(dict_list.__getitem__, sorted_indices))

# output
>>> sorted_stringList
['0.987 Algo. ABC', '0.345 Algo. BCD', '0.123 Algo. XYZ']
>>> sorted_dictList
[{'dict_abc': 'ABC'}, {'dict_bcd': 'BCD'}, {'dict_xyz': 'XYZ'}]
frietz58
  • 41
  • 4
1

I would like to suggest a solution if you need to sort more than 2 lists in sync:

def SortAndSyncList_Multi(ListToSort, *ListsToSync):
    y = sorted(zip(ListToSort, zip(*ListsToSync)))
    w = [n for n in zip(*y)]
    return list(w[0]), tuple(list(a) for a in zip(*w[1]))
0
newsource=[];newtarget=[]
for valueT in targetFiles:
    for valueS in sourceFiles:
            l1=len(valueS);l2=len(valueT);
            j=0
            while (j< l1):
                    if (str(valueT) == valueS[j:l1]) :
                            newsource.append(valueS)
                            newtarget.append(valueT)
                    j+=1
user10340258
  • 339
  • 1
  • 5
  • 15
  • 2
    a couple of lines of explanation would be helpful – saiedmomen Dec 18 '18 at 09:24
  • @saiedmomen I posted it in reference to https://stackoverflow.com/questions/53829160/sort-list-based-on-values-of-other-list-python-3-x Here target string is searched over the source string. – user10340258 Dec 18 '18 at 11:05
0

an algorithmic solution:

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']


lis = [(list1[i], list2[i]) for i in range(len(list1))]
list1.sort()
list2 = [x[1] for i in range(len(list1)) for x in lis if x[0] == i]

Outputs: -> Output speed: 0.2s

>>>list1
>>>[1, 1, 2, 3, 4]
>>>list2
>>>['one', 'one2', 'two', 'three', 'four']
Jundullah
  • 113
  • 2
  • 11
0

Based on @pylang's answer to the duplicate question I just closed, the necessary algorithm is implemented in the popular third-party library more_itertools, as sort_together.

Thus:

from more_itertools import sort_together

list1 = [3, 2, 4, 1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

list1, list2 = sort_together(list1, list2)
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
0

Sort two List Parllerly in python(Sorted By decimal value)

a = ['a','b','c','d','e','f']
b = ['0.23','80.00','5.01','6.58','1.38','79.06']
c=sorted(b,key=lambda x:float(x))
d=[]
for i in range(len(a)):
    d.append(a[b.index(c[i])])
Deepak
  • 1
  • 1