Сортировка списка на основе значений из другого списка?

370

У меня есть список строк, как это:

X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,   0,   1,   2,   2,   0,   1 ]

Какой самый короткий способ сортировки X, используя значения из Y, чтобы получить следующий вывод?

["a", "d", "h", "b", "c", "e", "i", "f", "g"]

Порядок элементов, имеющих одинаковый «ключ», не имеет значения. Я могу прибегнуть к использованию forконструкций, но мне любопытно, если есть более короткий путь. Какие-либо предложения?

легенда
источник
Ответ riza может быть полезен при построении данных, так как zip (* sorted (zip (X, Y), key = lambda pair: pair [0])) возвращает как отсортированные X, так и Y, отсортированные со значениями X.
jojo

Ответы:

479

Кратчайший код

[x for _,x in sorted(zip(Y,X))]

Пример:

X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,    0,   1,   2,   2,   0,   1]

Z = [x for _,x in sorted(zip(Y,X))]
print(Z)  # ["a", "d", "h", "b", "c", "e", "i", "f", "g"]

Вообще говоря

[x for _, x in sorted(zip(Y,X), key=lambda pair: pair[0])]

Разъяснение:

  1. zipдва listс.
  2. создать новый, отсортированный в listзависимости от zipиспользования sorted().
  3. используя понимание списка, извлеките первые элементы каждой пары из отсортированного, сжатого list.

Для получения дополнительной информации о том, как установить \ использовать keyпараметр, а также sortedфункцию в целом, взгляните на это .


Whatang
источник
117
Это правильно, но я добавлю примечание, что если вы пытаетесь отсортировать несколько массивов по одному и тому же массиву, это не обязательно будет работать должным образом, так как ключ, который используется для сортировки: (y, x) не только у Вместо этого следует использовать [x для (y, x) в отсортированном виде (zip (Y, X), ключ = лямбда-пара: pair [0])]
gms7777
1
хорошее решение! Но это должно быть так: список упорядочен относительно первого элемента пар, а понимание извлекает «второй» элемент пар.
MasterControlProgram
Это решение плохое, когда дело доходит до хранения. Сортировка на месте предпочтительнее, когда это возможно.
Hatefiend
107

Сожмите два списка вместе, рассортируйте их, затем возьмите нужные части:

>>> yx = zip(Y, X)
>>> yx
[(0, 'a'), (1, 'b'), (1, 'c'), (0, 'd'), (1, 'e'), (2, 'f'), (2, 'g'), (0, 'h'), (1, 'i')]
>>> yx.sort()
>>> yx
[(0, 'a'), (0, 'd'), (0, 'h'), (1, 'b'), (1, 'c'), (1, 'e'), (1, 'i'), (2, 'f'), (2, 'g')]
>>> x_sorted = [x for y, x in yx]
>>> x_sorted
['a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g']

Объедините их вместе, чтобы получить:

[x for y, x in sorted(zip(Y, X))]
Нед Бэтчелдер
источник
1
Это хорошо, если Xесть список str, но будьте осторожны, если есть возможность, которая <не определена для некоторых пар предметов X, например, - если некоторые из них былиNone
John La Rooy
1
Когда мы пытаемся использовать сортировку по объекту zip, AttributeError: 'zip' object has no attribute 'sort'это то , что я получаю сейчас.
Эш Упадхьяй
2
Вы используете Python 3. В Python 2 zip создал список. Теперь он производит итеративный объект. sorted(zip(...))должен все еще работать, или: them = list(zip(...)); them.sort()
Нед Бэтчелдер
77

Кроме того, если вы не возражаете против использования numpy-массивов (или на самом деле уже имеете дело с numpy-массивами ...), вот еще одно приятное решение:

people = ['Jim', 'Pam', 'Micheal', 'Dwight']
ages = [27, 25, 4, 9]

import numpy
people = numpy.array(people)
ages = numpy.array(ages)
inds = ages.argsort()
sortedPeople = people[inds]

Я нашел это здесь: http://scienceoss.com/sort-one-list-by-another-list/

Том
источник
1
Для больших массивов / векторов это решение с numpy выгодно!
MasterControlProgram
1
Если они уже являются массивами numpy, то это просто sortedArray1= array1[array2.argsort()]. И это также упрощает сортировку нескольких списков по определенному столбцу 2D-массива: например, sortedArray1= array1[array2[:,2].argsort()]для сортировки array1 (который может иметь несколько столбцов) по значениям в третьем столбце array2.
Аарон Брэмсон
40

Самым очевидным решением для меня является использование keyключевого слова arg.

>>> X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
>>> Y = [ 0,   1,   1,    0,   1,   2,   2,   0,   1]
>>> keydict = dict(zip(X, Y))
>>> X.sort(key=keydict.get)
>>> X
['a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g']

Обратите внимание, что вы можете сократить это до одной строки, если вы хотите:

>>> X.sort(key=dict(zip(X, Y)).get)
senderle
источник
2
Требуется ли для этого, чтобы значения в X были незаполненными?
Джек Пенг
15

Я на самом деле пришел сюда, чтобы отсортировать список по списку, где значения совпадают.

list_a = ['foo', 'bar', 'baz']
list_b = ['baz', 'bar', 'foo']
sorted(list_b, key=lambda x: list_a.index(x))
# ['foo', 'bar', 'baz']
nackjicholson
источник
1
Это исполнитель?
AFP_555
Понятия не имею. Сообщите, что вы нашли.
nackjicholson
1
Это плохая идея. indexвыполнит поиск O (N) по list_aрезультатам O(N² log N)сортировки.
Ричард
Спасибо, не делайте этого, когда производительность важна!
Нэкджичолсон
15

more_itertools имеет инструмент для параллельной сортировки итераций:

Данный

from more_itertools import sort_together


X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,    0,   1,   2,   2,   0,   1]

демонстрация

sort_together([Y, X])[1]
# ('a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g')
pylang
источник
13

Мне нравится иметь список отсортированных индексов. Таким образом, я могу отсортировать любой список в том же порядке, что и список источников. Как только у вас есть список отсортированных индексов, простое понимание списка сделает свое дело:

X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,    0,   1,   2,   2,   0,   1]

sorted_y_idx_list = sorted(range(len(Y)),key=lambda x:Y[x])
Xs = [X[i] for i in sorted_y_idx_list ]

print( "Xs:", Xs )
# prints: Xs: ["a", "d", "h", "b", "c", "e", "i", "f", "g"]

Обратите внимание, что отсортированный список индексов также можно получить с помощью numpy.argsort().

1-IJK
источник
12

Еще одна альтернатива, объединяющая несколько ответов.

zip(*sorted(zip(Y,X)))[1]

Чтобы работать на python3:

list(zip(*sorted(zip(B,A))))[1]
ТМС
источник
7

zip, сортировка по второму столбцу, возврат первого столбца.

zip(*sorted(zip(X,Y), key=operator.itemgetter(1)))[0]
риза
источник
Примечание: ключ = operator.itemgetter (1) решает проблему с дублированием
Кит
почтовый индекс не подписывается ... вы должны использоватьlist(zip(*sorted(zip(X,Y), key=operator.itemgetter(1))))[0]
Raphael
@ Что за дубликат?
Джош
Если найдено более одного совпадения, оно получает первое
Кит
3

Быстрый однострочник.

list_a = [5,4,3,2,1]
list_b = [1,1.5,1.75,2,3,3.5,3.75,4,5]

Скажем, вы хотите, чтобы список a соответствовал списку b.

orderedList =  sorted(list_a, key=lambda x: list_b.index(x))

Это полезно, когда нужно упорядочить меньший список со значениями в большем. Предполагая, что большой список содержит все значения в меньшем списке, это можно сделать.

Эван Лало
источник
Это не решает вопрос ОП. Вы пробовали это с примерами списков Xи Y?
Арье Лейб Таурог
Это плохая идея. indexвыполнит поиск O (N) по list_bрезультатам O(N² log N)сортировки.
Ричард
1

Вы можете создать pandas Series, используя первичный список как dataи другой список как index, а затем просто отсортировать по индексу:

import pandas as pd
pd.Series(data=X,index=Y).sort_index().tolist()

вывод:

['a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g']
Биньямин Эвен
источник
1

Вот ответ Whatangs, если вы хотите получить оба отсортированных списков (python3).

X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,    0,   1,   2,   2,   0,   1]

Zx, Zy = zip(*[(x, y) for x, y in sorted(zip(Y, X))])

print(list(Zx))  # [0, 0, 0, 1, 1, 1, 1, 2, 2]
print(list(Zy))  # ['a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g']

Просто помните, что Zx и Zy - это кортежи. Я также брожу, если есть лучший способ сделать это.

Предупреждение: если вы запускаете его с пустыми списками, он вылетает.

Ираклис Мутидис
источник
1

Я создал более общую функцию, которая сортирует более двух списков на основе другого, вдохновленного ответом @ Whatang.

def parallel_sort(*lists):
    """
    Sorts the given lists, based on the first one.
    :param lists: lists to be sorted

    :return: a tuple containing the sorted lists
    """

    # Create the initially empty lists to later store the sorted items
    sorted_lists = tuple([] for _ in range(len(lists)))

    # Unpack the lists, sort them, zip them and iterate over them
    for t in sorted(zip(*lists)):
        # list items are now sorted based on the first list
        for i, item in enumerate(t):    # for each item...
            sorted_lists[i].append(item)  # ...store it in the appropriate list

    return sorted_lists
pgmank
источник
0
list1 = ['a','b','c','d','e','f','g','h','i']
list2 = [0,1,1,0,1,2,2,0,1]

output=[]
cur_loclist = []

Чтобы получить уникальные значения, присутствующие в list2

list_set = set(list2)

Найти местоположение индекса в list2

list_str = ''.join(str(s) for s in list2)

Расположение индекса в list2отслеживается с помощьюcur_loclist

[0, 3, 7, 1, 2, 4, 8, 5, 6]

for i in list_set:
cur_loc = list_str.find(str(i))

while cur_loc >= 0:
    cur_loclist.append(cur_loc)
    cur_loc = list_str.find(str(i),cur_loc+1)

print(cur_loclist)

for i in range(0,len(cur_loclist)):
output.append(list1[cur_loclist[i]])
print(output)
VANI
источник
0

Это старый вопрос, но некоторые ответы, которые я вижу опубликованными, на самом деле не работают, потому что они zipне предназначены для сценариев. Другие ответы не удосужилисьimport operator и предоставить больше информации об этом модуле и его преимуществах здесь.

Есть как минимум две хорошие идиомы для этой проблемы. Начиная с примера ввода, который вы предоставили:

X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,   0,   1,   2,   2,   0,   1 ]

Использование выражения " Украсить-Сортировать-Украсить "

Это также известно как Schwartzian_transform после Р. Шварца, который популяризировал эту модель в Perl в 90-х годах:

# Zip (decorate), sort and unzip (undecorate).
# Converting to list to script the output and extract X
list(zip(*(sorted(zip(Y,X)))))[1]                                                                                                                       
# Results in: ('a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g')

Обратите внимание, что в этом случае Yи Xсортируются и сравниваются лексикографически. То есть Yсравниваются первые элементы (из ); и если они одинаковы, то Xсравниваются вторые элементы (из ) и так далее. Это может создать нестабильную выходные данные, если вы не включите исходные индексы списка для лексикографического порядка, чтобы сохранить дубликаты в их первоначальном порядке.

Использование operatorмодуля

Это дает вам более прямой контроль над тем, как сортировать ввод, так что вы можете получить стабильность сортировки , просто указав конкретный ключ для сортировки. Смотрите больше примеров здесь .

import operator    

# Sort by Y (1) and extract X [0]
list(zip(*sorted(zip(X,Y), key=operator.itemgetter(1))))[0]                                                                                                 
# Results in: ('a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g')
Амелио Васкес-Рейна
источник