Jak sortować dwie listy (które odnoszą się do siebie) w dokładnie ten sam sposób

139

Powiedzmy, że mam dwie listy:

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

Jeśli uruchomię list1.sort(), posortuje to, [1,1,2,3,4]ale czy istnieje również sposób na list2zsynchronizowanie (więc mogę powiedzieć, że element 4należy 'three')? Tak więc oczekiwany wynik to:

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

Mój problem polega na tym, że mam dość złożony program, który działa dobrze z listami, ale muszę zacząć odwoływać się do niektórych danych. Wiem, że jest to idealna sytuacja dla słowników, ale staram się unikać słowników podczas przetwarzania, ponieważ muszę sortować kluczowe wartości (jeśli muszę używać słowników, wiem, jak ich używać).

Zasadniczo charakter tego programu polega na tym, że dane są w losowej kolejności (jak powyżej), muszę je posortować, przetworzyć, a następnie wysłać wyniki (kolejność nie ma znaczenia, ale użytkownicy muszą wiedzieć, do którego wyniku należy klucz). Myślałem o umieszczeniu go najpierw w słowniku, a potem posortowaniu listy, ale nie miałbym możliwości rozróżnienia pozycji o tej samej wartości, gdyby nie zachowano kolejności (może to mieć wpływ na komunikowanie wyników użytkownikom). Więc idealnie, gdy już otrzymam listy, wolałbym wymyślić sposób posortowania obu list razem. czy to możliwe?

Błąd 404
źródło
Powinienem zwrócić uwagę, że twoje zmienne w list2 nie wskazują na ints w list1. Np. Jeśli zmienisz wartość taką jak list1 [0] = 9 i spojrzysz na list2, lista2 [0] nadal będzie równa 3. W przypadku liczb całkowitych w Pythonie nie używa odwołania / wskaźnika, kopiuje wartość. Byłoby lepiej, gdybyś wybrał list2 = list1 [:]
robert king

Odpowiedzi:

242

Klasycznym podejściem do tego problemu jest użycie idiomu „dekoruj, sortuj, dekoruj”, co jest szczególnie proste dzięki wbudowanej zipfunkcji Pythona :

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

To oczywiście nie są już listy, ale jeśli ma to znaczenie, można to łatwo naprawić:

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

Warto zauważyć, że powyższe może poświęcać szybkość na rzecz zwięzłości; wersja lokalna, która zajmuje 3 wiersze, jest odrobinę szybsza na moim komputerze w przypadku małych list:

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

Z drugiej strony, w przypadku większych list, wersja jednowierszowa mogłaby być szybsza:

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

Jak wskazuje Quantum7, sugestia JSF jest jeszcze trochę szybsza, ale prawdopodobnie będzie tylko trochę szybsza, ponieważ Python używa wewnętrznie tego samego idiomu DSU dla wszystkich typów opartych na kluczach. To się dzieje trochę bliżej gołego metalu. (To pokazuje, jak dobrze zoptymalizowane zipsą procedury!)

Myślę, że zippodejście oparte na zasadzie jest bardziej elastyczne i jest trochę bardziej czytelne, więc wolę je.

nadawca
źródło
6
co oznacza gwiazdka w trzecim wierszu?
Jeffrey,
8
Aby rozwinąć powyższe, *operator rozpakowuje argumenty ,
nadawca
1
Posortowany paradygmat indeksu / mapy zasugerowany przez JF Sebastiana jest około 10% szybszy niż którekolwiek rozwiązanie zip dla mnie (przy użyciu list 10000 losowych liczb całkowitych):% timeit index = range (len (l1)); index.sort (key = l1 .__ getitem__); map (l1 .__ getitem__, indeks); map (l2 .__ getitem__, index) 100 pętli, best of 3: 8,04 ms na pętlę (w porównaniu z 9,17 ms, 9,07 ms dla czasów nadawcy)
Quantum7
1
Pierwszy i drugi zip w list1, list2 = zip (* sort (zip (list1, list2))) robią tak różne rzeczy. * Robi różnicę.
ashu
1
@ashu, w pewnym sensie, tak! Ale w innym sensie prawie się nie różnią. zip(*x)ma interesującą własność, że jest swoją własną odwrotnością: l = [(1, 2), (3, 4)]; list(zip(*zip(*l))) == lzwraca True. W rzeczywistości jest operatorem transpozycji. zip()sam w sobie jest tym samym operatorem, ale zakłada, że ​​ręcznie rozpakowałeś sekwencję wejściową.
senderle
30

Indeksy można sortować, używając wartości jako kluczy:

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

Aby uzyskać posortowane listy z posortowanymi indeksami:

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

W twoim przypadku nie powinieneś mieć list1, list2a raczej jedną listę par:

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

Jest łatwy do stworzenia; w Pythonie łatwo jest sortować:

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

Sortuj tylko według pierwszej wartości:

data.sort(key=lambda pair: pair[0])
jfs
źródło
Fajną rzeczą w tym jest to, że mogę przechowywać indeksy i sortować inne rzeczy później, na wypadek, gdyby lista1 była ważną współrzędną, która wpływa na kilka innych tablic.
EL_DON
3
indexes = list (range (len (list1))) for python 3
DonQuiKong
@DonQuiKong, które musisz również list() obejść, map()jeśli chcesz użyć tego kodu w Pythonie 3.
jfs
Albo zamiast tego sorted_list1 = list(map(list1.__getitem__, indexes))można sorted_list1 = [list1[i] for i in indexes].
Nathan
20

Korzystałem z odpowiedzi udzielonej przez senderle przez długi czas, dopóki nie odkryłem np.argsort. Oto jak to działa.

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

Uważam, że to rozwiązanie jest bardziej intuicyjne i działa naprawdę dobrze. Wydajność:

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

Chociaż np.argsortnie jest najszybszy, jest dla mnie łatwiejszy w użyciu.

Daniel Thaagaard Andreasen
źródło
1
TypeError: only integer arrays with one element can be converted to an indexPodczas uruchamiania Twojego przykładu pojawia się błąd: (Python 2.7.6, numpy 1.8.2). Aby to naprawić, lista1 i lista2 muszą być zadeklarowane jako tablice numpy.
BenB,
Dzięki. Czy to nie jest to, co piszę w komentarzu w funkcji? W każdym razie myślę, że to głupie, że np.argsortnie próbuj nawracać się np.arraywewnętrznie.
Daniel Thaagaard Andreasen
Odnosiłem się do pierwszego fragmentu kodu, ponieważ nie działa tak, jak napisano :)
BenB,
Poprawiłem to, konwertując listy, gdy są przypisane do tablic numpy. Dzięki za komentarz :)
Daniel Thaagaard Andreasen
Teraz są dwukrotnie konwertowane na tablice Numpy;)
BenB
13

Transformacja Schwartza . Wbudowane sortowanie w Pythonie jest stabilne, więc oba 1nie powodują problemu.

>>> 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')]
Karl Knechtel
źródło
2
Jeśli jednak uznasz, że musisz to zrobić, zdecydowanie powinieneś ponownie rozważyć posiadanie dwóch „równoległych” list danych, w przeciwieństwie do utrzymywania listy 2-krotek (par) ... a może nawet faktycznego tworzenia klasy .
Karl Knechtel
3

Co powiesz na:

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')]
Artsiom Rudzenka
źródło
2

Aby to osiągnąć, możesz użyć funkcji zip()i 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']

Mam nadzieję że to pomoże

Hunter McMillen
źródło
2

Możesz użyć argumentu klucza w metodzie sort (), chyba że masz dwie takie same wartości w liście2.

Kod jest podany poniżej:

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

Sortuje listę2 według odpowiednich wartości z listy1, ale upewnij się, że podczas korzystania z niej żadne dwie wartości z listy2 nie są równe, ponieważ funkcja list.index () podaje pierwszą wartość

Saurav Yadav
źródło
posortowane jest nieco powolne w pewnych warunkach, chociaż działa.
tyan
2

Jednym ze sposobów jest śledzenie, dokąd przechodzi każdy indeks, poprzez sortowanie tożsamości [0,1,2, .. n]

Działa to dla dowolnej liczby list.

Następnie przenieś każdy element na jego miejsce. Najlepiej używać złączy.

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

Zauważ, że mogliśmy iterować listy bez ich sortowania:

list1_iter = (list1[i] for i in index)
Robert King
źródło
1

Jeśli używasz numpy, możesz użyć, np.argsortaby pobrać posortowane indeksy i zastosować je do listy. Działa to dla dowolnej liczby list, które chcesz posortować.

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])
Kurtis Streutker
źródło
0

rozwiązanie algorytmiczne:

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]

Wyjścia: -> Prędkość wyjściowa: 0.2s

>>>list1
>>>[1, 1, 2, 3, 4]
>>>list2
>>>['one', 'one2', 'two', 'three', 'four']
Jundullah
źródło
0

Inne podejście do zachowania porządku listy ciągów podczas sortowania według innej listy jest następujące:

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)

wynik

['one', 'one2', 'two', 'three', 'four']
[1, 1, 2, 3, 4]
śmierdziel
źródło
0

Chciałbym rozszerzyć odpowiedź otwartego jfs , która świetnie się sprawdziła w moim problemie: sortowanie dwóch list według trzeciej, ozdobionej listy :

Naszą dekorowaną listę możemy stworzyć w dowolny sposób, ale w tym przypadku stworzymy ją z elementów jednej z dwóch oryginalnych list, które chcemy posortować:

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

Teraz możemy zastosować rozwiązanie jfs, aby posortować nasze dwie listy według trzeciej

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

Edycja: Hej chłopaki, napisałem o tym blokowy post, sprawdź to, jeśli masz na to ochotę :) 🐍🐍🐍

frietz58
źródło
-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
user10340258
źródło
2
pomocne byłoby kilka linijek wyjaśnień
saiedmomen,
@saiedmomen Opublikowałem to w odniesieniu do stackoverflow.com/questions/53829160/ ... Tutaj docelowy ciąg jest przeszukiwany przez ciąg źródłowy.
user10340258