Подтвердить что ты не робот

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)]

Любые идеи?

4b9b3361

Ответ 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