Python - Сортировка списка кортежей с другим списком
У меня есть список кортежей to_order
, например:
to_order = [(0, 1), (1, 3), (2, 2), (3,2)]
И список, который дает приказ применить ко второму элементу каждого кортежа to_order
:
order = [2, 1, 3]
Итак, я ищу способ получить этот вывод:
ordered_list = [(2, 2), (3,2), (0, 1), (1, 3)]
Любые идеи?
Ответы
Ответ 1
Алгоритм
Вы можете распространять кортежи в дикторе списков в соответствии со вторым элементом и перебирать индексы order
, чтобы получить отсортированный список:
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
или index
не нужны, и выход стабилен.
Этот алгоритм похож на mapping
, упомянутый в предполагаемом дубликате. Этот связанный ответ работает только в том случае, если to_order
и order
имеют одинаковую длину, что не относится к вопросу OP.
Производительность
Этот алгоритм повторяется дважды по каждому элементу to_order
. Сложность O(n)
. Первый алгоритм @alfasin намного медленнее (O(n * m * log n)
), но его второй также O(n)
.
Здесь список с 10000 случайными парами между 0
и 1000
. Мы извлекаем уникальные второстепенные элементы и перетасовываем их, чтобы определить 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
Ответ 2
Вы можете указать key
, который будет проверять индекс (второго элемента) в order
и сортировать на нем:
to_order = [(0, 1), (1, 3), (2, 2), (3,2)]
order = [2, 1, 3]
print(sorted(to_order, key=lambda item: order.index(item[1]))) # [(2, 2), (3, 2), (0, 1), (1, 3)]
ИЗМЕНИТЬ
Так как обсуждение временных сложностей началось... здесь ya go, следующий алгоритм работает в O(n+m)
, используя пример ввода Eric:
N = 5
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_sort(to_order, order):
bins = {}
for pair in to_order:
bins.setdefault(pair[1], []).append(pair)
return [pair for i in order for pair in bins[i]]
def alfasin_new_sort(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]
from timeit import timeit
print("eric_sort", timeit("eric_sort(to_order, order)", setup=setup, number=1000))
print("alfasin_new_sort", timeit("alfasin_new_sort(to_order, order)", setup=setup, number=1000))
ВЫВОД:
eric_sort 59.282021682999584
alfasin_new_sort 44.28244407700004
Ответ 3
Другое решение:
[item for key in order for item in filter(lambda x: x[1] == key, to_order)]
Это решение работает с order
сначала, фильтруя to_order
для каждого key
в order
.
Эквивалент:
ordered = []
for key in order:
for item in filter(lambda x: x[1] == key, to_order):
ordered.append(item)
Короче, но я не знаю, как это сделать со списком:
ordered = []
for key in order:
ordered.extend(filter(lambda x: x[1] == key, to_order))
Примечание. Это не будет вызывать ValueError
, если to_order
содержит кортеж x
, где x[1]
не находится в order
.
Ответ 4
Я лично предпочитаю функцию list
objects sort
, а не встроенную sort
, которая генерирует новый список, а не заменяет список на месте.
to_order = [(0, 1), (1, 3), (2, 2), (3,2)]
order = [2, 1, 3]
to_order.sort(key=lambda x: order.index(x[1]))
print(to_order)
>[(2, 2), (3, 2), (0, 1), (1, 3)]
Небольшое объяснение на пути: параметр key
метода сортировки в основном preprocesses
список и ranks
все значения, основанные на мера. В нашем случае order.index()
просматривает первое вхождение обработанного в данный момент элемента и возвращает его позицию.
x = [1,2,3,4,5,3,3,5]
print x.index(5)
>4