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


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.