Як сортувати два списки (які посилаються один на одного) точно таким же чином


139

Скажіть, у мене є два списки:

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

Якщо я запускаю list1.sort(), це відсортує його, [1,1,2,3,4]але чи є спосіб list2синхронізуватись (так що я можу сказати, що елемент 4належить 'three')? Отже, очікуваний вихід буде таким:

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

Моя проблема полягає в тому, що у мене досить складна програма, яка добре працює зі списками, але мені якось потрібно починати посилатися на деякі дані. Я знаю, що це прекрасна ситуація для словників, але я намагаюся уникати словників при обробці, оскільки мені потрібно сортувати ключові значення (якщо я повинен використовувати словники, я знаю, як їх використовувати).

В основному характер цієї програми полягає в тому, що дані надходять у випадковому порядку (наприклад, вище), мені потрібно сортувати їх, обробляти та надсилати результати (замовлення не має значення, але користувачі повинні знати, до якого результату належить ключ). Я подумав про те, щоб спершу це зробити у словнику, а потім сортувати список, але я не мав би можливості диференціювати елементи в однаковому значенні, якщо порядок не підтримується (це може мати вплив при повідомленні результатів користувачам). Тому в ідеалі, як тільки я отримаю списки, я скоріше знайду спосіб сортування обох списків. Чи можливо це?


Я повинен зазначити, що ваші змінні в list2 не вказують на вставки в list1. Наприклад, якщо змінити таке значення, як list1 [0] = 9, і подивитись на list2, list2 [0] все одно буде 3. З цілими числами в python він не використовує посилання / покажчик, він копіює це значення. Вам краще було б перейти list2 = list1 [:]
— Роберт Кінг

Відповіді:


242

Класичним підходом до цієї проблеми є використання ідіоми "прикрасити, сортувати, підкреслити", що особливо просто за допомогою вбудованої zipфункції python :

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

Вони, звичайно, більше не є списками, але це легко виправити, якщо це має значення:

>>> list1, list2 = (list(t) for t in zip(*sorted(zip(list1, list2))))
>>> list1
[1, 1, 2, 3, 4]
>>> list2
['one', 'one2', 'two', 'three', 'four']

Варто відзначити, що вищезазначене може принести в жертву швидкість для лаконічності; версія на місці, що займає 3 рядки, на моїй машині швидше набирає невеликі списки:

>>> %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

З іншого боку, для більш великих списків однолінійна версія може бути швидшою:

>>> %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

Як зазначає Quantum7, пропозиція JSF все-таки трохи швидша, але це, мабуть, лише коли-небудь трохи швидше, тому що Python використовує внутрішню таку ж ідіому DSU для всіх типів на основі ключів. Це просто відбувається трохи ближче до голого металу. (Це показує, наскільки добре оптимізовані zipпроцедури!)

Я думаю, що zipпідхід на основі більш гнучкий і трохи читабельніший, тому я вважаю за краще.


6
що означає зірочка в третьому рядку?
— Джеффрі

8
Щоб детальніше *
— сказати

1
Впорядкована парадигма індексів / карт, запропонована Дж. Ф. Себастьяном, приблизно на 10% швидша, ніж будь-яке поштове рішення для мене (використовуючи списки 10000 випадкових інт):% timeit index = range (len (l1)); index.sort (ключ = l1 .__ getitem__); карта (l1 .__ getitem__, покажчик); map (l2 .__ getitem__, index) 100 циклів, найкраще 3: 8,04 мс на цикл (проти 9,17 мс, 9,07 мс для тимітів відправника)
— Quantum7

1
Перший і другий поштовий індекс у list1, list2 = zip (* сортований (zip (list1, list2))) роблять такі різні речі. Значення * робить всю різницю.
— ашу

1
@ashu, в певному сенсі, так! Але в іншому сенсі вони навряд чи різні. zip(*x)має цікаву властивість, що вона є власною зворотною: l = [(1, 2), (3, 4)]; list(zip(*zip(*l))) == lповертає True. Це фактично оператор транспозиції. zip()самостійно - це той самий оператор, але передбачається, що ви розпакували послідовність введення вручну.
— senderle

30

Ви можете сортувати індекси, використовуючи значення як ключі:

indexes = range(len(list1))
indexes.sort(key=list1.__getitem__)

Щоб отримати відсортовані списки з відсортованими індексами:

sorted_list1 = map(list1.__getitem__, indexes)
sorted_list2 = map(list2.__getitem__, indexes)

У вашому випадку у вас не повинно бути list1, list2а лише одного списку пар:

data = [(3, 'three'), (2, 'two'), (4, 'four'), (1, 'one'), (1, 'one2')]

Це легко створити; сортувати в Python легко:

data.sort() # sort using a pair as a key

Сортувати лише за першим значенням:

data.sort(key=lambda pair: pair[0])

Холодна річ у тому, що я можу зберігати індекси навколо та сортувати інші речі пізніше, якщо list1 є важливою координатою, яка впливає на кілька інших масивів.
— EL_DON

3
indexes = list (діапазон (len (list1))) для python 3
— DonQuiKong

@DonQuiKong ви також повинні list() навколо , map()якщо ви хочете використовувати цей код в Python 3.
— JFS

Або замість цього sorted_list1 = list(map(list1.__getitem__, indexes)) одного можна було б зробити sorted_list1 = [list1[i] for i in indexes].
— Натан

20

Я довго використовував відповідь, яку дав senderle, поки не виявив np.argsort. Ось як це працює.

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

Я вважаю це рішення більш інтуїтивним, і воно працює дуже добре. Виконання:

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

Хоча np.argsortце і не найшвидший, я вважаю його простішим у використанні.


1
У мене з’являється помилка запуску вашого прикладу: TypeError: only integer arrays with one element can be converted to an index(Python 2.7.6, numpy 1.8.2). Щоб виправити це, list1 та list2 повинні бути оголошені масивними масивами.
— BenB

Дякую. Хіба це не те, що я пишу в коментарі у функції? У всякому разі, я думаю, що це нерозумно, що np.argsortне намагаються перетворитись на np.arrayвнутрішню.
— Даніель Таагаард Андреасен

Я мав на увазі перший фрагмент коду, оскільки він не працює так, як написано :)
— BenB

Я виправив це шляхом перетворення списків, коли вони присвоєні нумеровим масивам. Дякую за коментар :)
— Daniel Thaagaard Andreasen

Тепер вони перетворюються на масиви Numpy двічі;)
— BenB

13

Перетворення Шварца . Вбудована сортування Python стабільна, тому два 1s не створюють проблем.

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

2
Однак, якщо вам здається, що вам потрібно це зробити, вам слід наполегливо переосмислити наявність двох "паралельних" списків даних на відміну від збереження списку 2-кортежів (пар) ... або, можливо, навіть фактично створення класу .
— Карл Кнечтел

3

А як на рахунок:

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

2

Ви можете скористатися функціями zip()та sort()для цього:

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

Сподіваюся, це допомагає


2

Ви можете використовувати ключовий аргумент методом sorted (), якщо у списку немає двох однакових значень.

Код наведено нижче:

sorted(list2, key = lambda x: list1[list2.index(x)]) 

Він сортує list2 за відповідними значеннями в list1, але переконайтесь, що при використанні цього значення два значення в list2 не оцінюються як рівні, оскільки функція list.index () дає перше значення


сортування дещо повільне в деякому стані, хоча воно працює.
— tyan

2

Один із способів - відстежити, куди йде кожен індекс, сортуючи ідентичність [0,1,2, .. n]

Це працює для будь-якої кількості списків.

Потім перемістіть кожен предмет у своє положення. Використання сплайсів найкраще.

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']"

Зауважте, що ми могли повторити списки, навіть не сортуючи їх:

list1_iter = (list1[i] for i in index)

1

Якщо ви використовуєте numpy, ви можете використовувати np.argsortдля отримання відсортованих індексів і застосувати ці індекси до списку. Це працює для будь-якої кількості списку, яку ви хочете б сортувати.

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

0

алгоритмічне рішення:

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]

Виходи: -> Вихідна швидкість: 0.2s

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

0

Інший підхід до збереження порядку рядкового списку при сортуванні по іншому списку такий:

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)

вихід

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

0

Я хотів би розширити відповідь відкритого jfs , яка добре спрацювала з моєю проблемою: сортування двох списків за третім, оформленим списком :

Ми можемо створити наш оформлений список будь-яким способом, але в цьому випадку створимо його з елементів одного з двох оригінальних списків, які ми хочемо сортувати:

# 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']

Тепер ми можемо застосувати рішення jfs для сортування наших двох списків по третьому

# 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'}]

Редагувати: Ей, хлопці, я створив блок-пост про це, перевірте, чи вам це здається :) :)


-1
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

2
корисними будуть кілька рядків пояснень
— saiedmomen

@saiedmomen Я опублікував це з посиланням на stackoverflow.com/questions/53829160/… Тут шукається цільовий рядок через вихідний рядок.
— користувач10340258
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.