How to Sort a List of Tuples According to Another List

Sort list of tuples based on another list

You can use sorted with an appropriate key function:

l1 = [(1, 'a', 'x'), (2, 'b', 'y'), (3, 'a', 'z'), (4, 'c', 'xyz')]
l2 = ['a', 'b', 'c']
d2 = {v: i for i, v in enumerate(l2)} # map elements to indexes
# {'a': 0, 'b': 1, 'c': 2}

sorted(l1, key=lambda x: d2[x[1]])
# [(1, 'a', 'x'), (3, 'a', 'z'), (2, 'b', 'y'), (4, 'c', 'xyz')]

Sort tuple list with another list

Algorithm

You can distribute the tuples in a dict of lists according to the second element and iterate over order indices to get the sorted list:

from collections import defaultdict
to_order = [(0, 1), (1, 3), (2, 2), (3, 2)]
order = [2, 1, 3]

bins = defaultdict(list)

for pair in to_order:
bins[pair[1]].append(pair)

print(bins)
# defaultdict(<class 'list'>, {1: [(0, 1)], 3: [(1, 3)], 2: [(2, 2), (3, 2)]})

print([pair for i in order for pair in bins[i]])
# [(2, 2), (3, 2), (0, 1), (1, 3)]

sort or index aren't needed and the output is stable.

This algorithm is similar to the mapping mentioned in the supposed duplicate. This linked answer only works if to_order and order have the same lengths, which isn't the case in OP's question.

Performance

This algorithm iterates twice over each element of to_order. The complexity is O(n). @alfasin's first algorithm is much slower (O(n * m * log n)), but his second one is also O(n).

Here's a list with 10000 random pairs between 0 and 1000. We extract the unique second elements and shuffle them in order to define order:

from random import randrange, shuffle
from collections import defaultdict
from timeit import timeit
from itertools import chain

N = 1000
to_order = [(randrange(N), randrange(N)) for _ in range(10*N)]
order = list(set(pair[1] for pair in to_order))
shuffle(order)

def eric(to_order, order):
bins = defaultdict(list)
for pair in to_order:
bins[pair[1]].append(pair)
return list(chain.from_iterable(bins[i] for i in order))

def alfasin1(to_order, order):
arr = [[] for i in range(len(order))]
d = {k:v for v, k in enumerate(order)}
for item in to_order:
arr[d[item[1]]].append(item)
return [item for sublist in arr for item in sublist]

def alfasin2(to_order, order):
return sorted(to_order, key=lambda item: order.index(item[1]))

print(eric(to_order, order) == alfasin1(to_order, order))
# True
print(eric(to_order, order) == alfasin2(to_order, order))
# True

print("eric", timeit("eric(to_order, order)", globals=globals(), number=100))
# eric 0.3117517130003762
print("alfasin1", timeit("alfasin1(to_order, order)", globals=globals(), number=100))
# alfasin1 0.36100843100030033
print("alfasin2", timeit("alfasin2(to_order, order)", globals=globals(), number=100))
# alfasin2 15.031453827000405

How to sort a list/tuple of lists/tuples by the element at a given index?

sorted_by_second = sorted(data, key=lambda tup: tup[1])

or:

data.sort(key=lambda tup: tup[1])  # sorts in place

The default sort mode is ascending. To sort in descending order use the option reverse=True:

sorted_by_second = sorted(data, key=lambda tup: tup[1], reverse=True)

or:

data.sort(key=lambda tup: tup[1], reverse=True)  # sorts in place

How to sort a list of tuples according to another list

a.sort(key=lambda x: b.index(x[0]))

This sorts a in-place using the the index in b of the first element of each tuple from a as the values it sorts on.

Another, possibly cleaner, way of writing it would be:

a.sort(key=lambda (x,y): b.index(x))

If you had large numbers of items, it might be more efficient to do things a bit differently, because .index() can be an expensive operation on a long list, and you don't actually need to do a full sorting since you already know the order:

mapping = dict(a)
a[:] = [(x,mapping[x]) for x in b]

Note that this will only work for a list of 2-tuples. If you want it to work for arbitrary-length tuples, you'd need to modify it slightly:

mapping = dict((x[0], x[1:]) for x in a)
a[:] = [(x,) + mapping[x] for x in b]

how to sort a list of tuples based on another list

You can use sorted function, just like below.

sorted(Y, key=lambda a: (a[1], X.index(a[0])))

The order of keys is (Y based number, X based character).

Sort a list of tuples by 2nd item (integer value)

Try using the key keyword with sorted().

sorted(
[('abc', 121), ('abc', 231), ('abc', 148), ('abc', 221)],
key=lambda x: x[1]
)

key should be a function that identifies how to retrieve the comparable element from your data structure. In your case, it is the second element of the tuple, so we access [1].

For optimization, see jamylak's response using itemgetter(1), which is essentially a faster version of lambda x: x[1].

Sorting a list of tuples in different order based on the values of a tuple

You can change the signs in the tuples' values to get the expected behaviour:

ts.sort(key=lambda t: (t[0], -t[1], -t[2]))

print(ts)
# [(3, 12, 9), (3, 12, 8), (4, 22, 17), (4, 5, 7), (4, 5, 6), (7, 14, 5),
# (20, 23, 24), (20, 22, 8)]

For the general case, you can map the the increasing', 'decreasing'... list to sings and zip each tuple in the key with the signs as:

l = ('increasing', 'decreasing', 'decreasing')
d = {'increasing':1, 'decreasing':-1}
signs = [d[i] for i in l]
ts.sort(key = lambda x: tuple(i*sign for sign,i in zip(signs, x)))

Which would yield the same as above:

print(ts)
# [(3, 12, 9), (3, 12, 8), (4, 22, 17), (4, 5, 7), (4, 5, 6), (7, 14, 5),
# (20, 23, 24), (20, 22, 8)]


Related Topics



Leave a reply



Submit