Jak przeszukiwać listę krotek w Pythonie

91

Mam więc listę krotek, takich jak ta:

[(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")]

Chcę mieć tę listę dla krotki, której wartość liczbowa jest równa czemuś.

Więc jeśli to zrobię search(53), zwróci wartość indeksu2

Czy jest na to łatwy sposób?

hdx
źródło

Odpowiedzi:

95
[i for i, v in enumerate(L) if v[0] == 53]
Ignacio Vazquez-Abrams
źródło
69
Czy mógłbyś wyjaśnić?
schatten
17
Wyjaśnione słowami: Dla każdego i, v w wyliczonej liście L (co sprawia, że ​​i jest pozycją elementu na wyliczonej liście i v jest oryginalną krotką) sprawdź, czy pierwszy element krotki ma wartość 53, jeśli tak, dołącz wynik kodu przed 'for' do nowo utworzonej listy, tutaj: i. Może to być również moja_funkcja (i, v) lub jeszcze jedno rozumienie listy. Ponieważ twoja lista krotek ma tylko jedną krotkę z 53 jako pierwszą wartością, otrzymasz listę z jednym elementem.
djangonaut
6
Po prostu dodałbym [i for i, v in enumerate (L) if v [0] == 53] .pop (), aby mieć wartość int.
alemol
50

tl; dr

Wyrażenie generator jest prawdopodobnie najbardziej wydajnych i proste rozwiązanie problemu:

l = [(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")]

result = next((i for i, v in enumerate(l) if v[0] == 53), None)
# 2

Wyjaśnienie

Istnieje kilka odpowiedzi, które zapewniają proste rozwiązanie tego pytania ze zrozumieniem listy. Chociaż te odpowiedzi są całkowicie poprawne, nie są optymalne. W zależności od przypadku użycia wprowadzenie kilku prostych modyfikacji może przynieść znaczące korzyści.

Główny problem, jaki widzę przy używaniu funkcji rozumienia listy w tym przypadku użycia, polega na tym, że cała lista zostanie przetworzona, chociaż chcesz znaleźć tylko 1 element .

Python zapewnia prostą konstrukcję, która jest tutaj idealna. Nazywa się to wyrażeniem generatora . Oto przykład:

# Our input list, same as before
l = [(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")]

# Call next on our generator expression.
next((i for i, v in enumerate(l) if v[0] == 53), None)

Możemy oczekiwać, że ta metoda będzie działać w zasadzie tak samo jak składanie list w naszym trywialnym przykładzie, ale co, jeśli pracujemy z większym zestawem danych? W tym miejscu pojawia się zaleta korzystania z metody generatora. Zamiast tworzyć nową listę, użyjemy istniejącej listy jako naszej iterowalnej i użyjemy next()do pobrania pierwszego elementu z naszego generatora.

Przyjrzyjmy się, jak te metody działają inaczej na niektórych większych zbiorach danych. Są to duże listy, składające się z 10000000 + 1 elementów, z naszym celem na początku (najlepsza) lub na końcu (najgorsza). Możemy sprawdzić, czy obie te listy będą działać jednakowo, korzystając z następującego rozumienia list:

Lista zdań

"Najgorszy przypadek"

worst_case = ([(False, 'F')] * 10000000) + [(True, 'T')]
print [i for i, v in enumerate(worst_case) if v[0] is True]

# [10000000]
#          2 function calls in 3.885 seconds
#
#    Ordered by: standard name
#
#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
#         1    3.885    3.885    3.885    3.885 so_lc.py:1(<module>)
#         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

„Najlepszy przypadek”

best_case = [(True, 'T')] + ([(False, 'F')] * 10000000)
print [i for i, v in enumerate(best_case) if v[0] is True]

# [0]
#          2 function calls in 3.864 seconds
#
#    Ordered by: standard name
#
#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
#         1    3.864    3.864    3.864    3.864 so_lc.py:1(<module>)
#         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

Wyrażenia generatora

Oto moja hipoteza dotycząca generatorów: zobaczymy, że generatory będą działać znacznie lepiej w najlepszym przypadku, ale podobnie w najgorszym. Ten wzrost wydajności wynika głównie z faktu, że generator jest oceniany leniwie, co oznacza, że ​​oblicza tylko to, co jest wymagane do uzyskania wartości.

Najgorszy przypadek

# 10000000
#          5 function calls in 1.733 seconds
#
#    Ordered by: standard name
#
#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
#         2    1.455    0.727    1.455    0.727 so_lc.py:10(<genexpr>)
#         1    0.278    0.278    1.733    1.733 so_lc.py:9(<module>)
#         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
#         1    0.000    0.000    1.455    1.455 {next}

Najlepszy przypadek

best_case  = [(True, 'T')] + ([(False, 'F')] * 10000000)
print next((i for i, v in enumerate(best_case) if v[0] == True), None)

# 0
#          5 function calls in 0.316 seconds
#
#    Ordered by: standard name
#
#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
#         1    0.316    0.316    0.316    0.316 so_lc.py:6(<module>)
#         2    0.000    0.000    0.000    0.000 so_lc.py:7(<genexpr>)
#         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
#         1    0.000    0.000    0.000    0.000 {next}

CO?! Najlepszy przypadek niszczy listy składane, ale nie spodziewałem się, że nasz najgorszy przypadek będzie lepszy od rozumienia list w takim stopniu. W jaki sposób? Szczerze mówiąc, mogłem tylko spekulować bez dalszych badań.

Weź to wszystko z przymrużeniem oka, nie przeprowadziłem tutaj żadnego solidnego profilowania, tylko kilka bardzo podstawowych testów. Powinno to wystarczyć, aby zdać sobie sprawę, że wyrażenie generatora jest bardziej wydajne dla tego typu wyszukiwania list.

Zauważ, że to wszystko jest podstawowym, wbudowanym Pythonem. Nie musimy niczego importować ani używać żadnych bibliotek.

Po raz pierwszy zobaczyłem tę technikę wyszukiwania na kursie Udacity cs212 z Peterem Norvigiem.

Jon Surrell
źródło
2
ciekawe, przetestowałem i stwierdziłem, że jest naprawdę szybki
Grijesh Chauhan
3
To powinna być akceptowana odpowiedź. Wyrażenia generatora nie materializują całej sekwencji wyjściowej, gdy są uruchamiane, ale raczej obliczają je do iteratora, który zwraca jeden element na raz z wyrażenia.
BoltzmannBrain
2
To jest świetne, znacznie szybsze niż zrozumienie listy w moim przypadku, dzięki!
mindm49907
49

Możesz użyć rozumienia listy :

>>> a = [(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")]
>>> [x[0] for x in a]
[1, 22, 53, 44]
>>> [x[0] for x in a].index(53)
2
Greg Hewgill
źródło
29

Twoje krotki to w zasadzie pary klucz-wartość - python - dicttak:

l = [(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")]
val = dict(l)[53]

Edycja - aha, mówisz, że chcesz mieć wartość indeksu (53, "xuxa"). Jeśli naprawdę tego chcesz, będziesz musiał przejrzeć oryginalną listę lub stworzyć bardziej skomplikowany słownik:

d = dict((n,i) for (i,n) in enumerate(e[0] for e in l))
idx = d[53]
Andrew Jaffe
źródło
2
Jeśli będziemy ignorować to, co faktycznie poprosił o OP, myślę, że początkowa odpowiedź jest najlepszą odpowiedzią na „Jak przeszukiwać listę krotek w Pythonie”
Rick Westera
Twoja pierwsza odpowiedź była przydatna do moich celów. Może lepiej jednak użyć .get (), na wypadek gdyby element nie był w dyktandzie. l = [(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")] val = dict(l).get(53)
user1503941
12

Hmm ... cóż, najprostszym sposobem, który przychodzi na myśl, jest przekształcenie tego w dyktando

d = dict(thelist)

i dostęp d[53].

EDYCJA : Ups, za pierwszym razem źle odczytałem pytanie. Wygląda na to, że faktycznie chcesz uzyskać indeks, w którym przechowywany jest podany numer. W takim razie spróbuj

dict((t[0], i) for i, t in enumerate(thelist))

zamiast zwykłej starej dictkonwersji. Wtedy d[53]byłoby 2.

David Z
źródło
6

Zakładając, że lista może być długa, a liczby mogą się powtarzać, rozważ użycie typu SortedList z modułu sortcontainers w języku Python . Typ SortedList automatycznie utrzyma krotki w kolejności według numeru i umożliwi szybkie wyszukiwanie.

Na przykład:

from sortedcontainers import SortedList
sl = SortedList([(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")])

# Get the index of 53:

index = sl.bisect((53,))

# With the index, get the tuple:

tup = sl[index]

Będzie to działać znacznie szybciej niż sugestia dotycząca zrozumienia listy, wykonując wyszukiwanie binarne. Sugestia ze słownika będzie jeszcze szybsza, ale nie będzie działać, jeśli mogą istnieć zduplikowane liczby z różnymi ciągami.

Jeśli istnieją zduplikowane liczby z różnymi ciągami, musisz zrobić jeszcze jeden krok:

end = sl.bisect((53 + 1,))

results = sl[index:end]

Dzieląc się na 54, znajdziemy indeks końcowy dla naszego wycinka. Będzie to znacznie szybsze na długich listach w porównaniu z zaakceptowaną odpowiedzią.

GrantJ
źródło
1

Po prostu inny sposób.

zip(*a)[0].index(53)
RussW
źródło
-2

[k dla k, v in l if v == ' delicia ']

oto lista krotek - [(1, "juca"), (22, "james"), (53, "xuxa"), (44, "delicia")]

Zamiast konwertować to na dykt, używamy llist rozumienia.

*Key* in Key,Value in list, where value = **delicia**

Mantej Singh
źródło
Tak, oczywiście. Dziękuję @cosmoonot.
Mantej Singh
tutaj jest lista krotek - [(1, "juca"), (22, "james"), (53, "xuxa"), (44, "delicia")] Zamiast konwertować ją na dyktę, używamy llist rozumienia. ` Key w klucz, wartość w liście, w którym wartość = delicia `
Mantej Singh