194

Say I have two lists:

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

If I run list1.sort(), it'll sort it to [1,1,2,3,4] but is there a way to get list2 in sync as well (so I can say item 4 belongs to 'three')? So, the expected output would be:

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

My problem is I have a pretty complex program that is working fine with lists but I sort of need to start referencing some data. I know this is a perfect situation for dictionaries but I'm trying to avoid dictionaries in my processing because I do need to sort the key values (if I must use dictionaries I know how to use them).

Basically the nature of this program is, the data comes in a random order (like above), I need to sort it, process it and then send out the results (order doesn't matter but users need to know which result belongs to which key). I thought about putting it in a dictionary first, then sorting list one but I would have no way of differentiating of items in the with the same value if order is not maintained (it may have an impact when communicating the results to users). So ideally, once I get the lists I would rather figure out a way to sort both lists together. Is this possible?

Georgy
  • 9,972
  • 7
  • 57
  • 66
Lostsoul
  • 23,230
  • 42
  • 133
  • 217
  • 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[:] – robert king Mar 19 '12 at 03:10

14 Answers14

328

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
  • 235,058
  • 25
  • 372
  • 444
senderle
  • 136,589
  • 35
  • 205
  • 230
  • 6
    what does the asterisk in the third line represent? – Jeffrey Mar 19 '12 at 05:25
  • 8
    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
  • 1
    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
  • 1
    @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
  • 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
  • @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
40

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
  • 374,366
  • 172
  • 933
  • 1,594
  • 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
  • 6
    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
  • 1
    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
28

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
14

Schwartzian transform. 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
  • 56,349
  • 8
  • 83
  • 124
  • 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
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,100
  • 8
  • 13
4

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)
robert king
  • 15,381
  • 8
  • 94
  • 111
3

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
  • 56,682
  • 21
  • 115
  • 164
  • 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
3

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
  • 26,491
  • 4
  • 32
  • 51
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
  • 8
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
  • 21
  • 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
  • 189
  • 5
  • 14
  • 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
  • 115
  • 2
  • 11