Як шукати у списку кортежів на Python


90

Отже, у мене є список таких кортежів, як цей:

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

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

Так що якщо я search(53)це зроблю, це поверне значення індексу2

Чи є простий спосіб це зробити?

Відповіді:


94
[i for i, v in enumerate(L) if v[0] == 53]

68
Поясніть, будь ласка?
schatten

17
Пояснено словами: Для кожного i, v у переліченому списку L (що робить i позицію елемента в перерахованому списку та v вихідний кортеж) перевірте, чи є перший елемент кортежу 53, якщо так, додайте результат коду перед "за" до новоствореного списку, тут: i. Це також може бути моя_функція (i, v) або ще одне розуміння списку. Оскільки у вашому списку кортежів лише один кортеж із 53 першим значенням, ви отримаєте список з одним елементом.
djangonaut

6
Я просто додав би [i для i, v в enumerate (L), якщо v [0] == 53] .pop (), щоб мати значення int.
alemol


47

tl; д-р

вираз генератора , ймовірно , є найбільш продуктивним і простим рішенням вашої проблеми:

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

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

Пояснення

Є кілька відповідей, які дають просте рішення цього питання із розумінням списку. Хоча ці відповіді абсолютно правильні, вони не є оптимальними. Залежно від випадку використання, ви можете зробити кілька простих модифікацій.

Основна проблема, яку я бачу при використанні розуміння списку для цього випадку, полягає в тому, що весь список буде оброблений, хоча ви хочете знайти лише 1 елемент .

Python пропонує просту конструкцію, яка тут ідеально підходить. Це називається генераторним виразом . Ось приклад:

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

Ми можемо розраховувати, що цей метод виконує в основному те саме, що і розуміння списків у нашому тривіальному прикладі, але що, якщо ми працюємо з більшим набором даних? Ось тут і виграє перевага використання методу генератора. Замість того, щоб створювати новий список, ми використаємо ваш існуючий список як наш ітеруваний і використаємо next()для отримання першого елемента з нашого генератора.

Давайте розглянемо, як ці методи по-різному виконують деякі більші набори даних. Це великі списки, складені з 10000000 + 1 елементів, з нашою ціллю на початку (найкраще) або в кінці (найгірше). Ми можемо перевірити, що обидва ці списки будуть однаково ефективні, використовуючи таке розуміння списку:

Список розумінь

"Найгірший випадок"

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}

"Кращий випадок"

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}

Вирази генератора

Ось моя гіпотеза щодо генераторів: ми побачимо, що генератори будуть значно ефективнішими в кращому випадку, але так само в гіршому випадку. Цей приріст продуктивності здебільшого пов’язаний з тим, що генератор оцінюється ліниво, тобто він обчислює лише те, що потрібно для отримання значення.

Найгірший випадок

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

Кращий випадок

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}

ЩО?! Найкращий випадок здувається розуміння списку, але я не очікував, що наш найгірший випадок перевершить розуміння списку до такої міри. Як у тому, що? Чесно кажучи, я міг лише припускати, без подальших досліджень.

Візьміть все це з достатньою кількістю солі, я не проводив тут жодного надійного профілювання, лише деякі найпростіші тести. Цього має бути достатньо, щоб зрозуміти, що вираз генератора є більш продуктивним для цього типу пошуку в списку.

Зверніть увагу, що це все базовий, вбудований python. Нам не потрібно нічого імпортувати або використовувати будь-які бібліотеки.

Я вперше побачив цю техніку для пошуку на курсі Udacity cs212 у Пітера Норвіга.


2
цікаво, я протестував і виявив, що це справді швидко
Грієш Чаухан,

3
Це має бути прийнятою відповіддю. Вирази генератора не матеріалізують всю вихідну послідовність під час їх запуску, а навпаки, вони обчислюють ітератор, який видає по одному елементу за один раз із виразу.
BoltzmannBrain

2
Це чудово, набагато швидше, ніж розуміння списку в моєму випадку, дякую!
mindm49907

29

Ваші кортежі - це в основному пари ключ-значення - пітон - dictso:

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

Редагувати - ага, ви кажете, що хочете значення індексу (53, "xuxa"). Якщо це дійсно те, що ви хочете, вам доведеться переглядати оригінальний список або, можливо, скласти більш складний словник:

d = dict((n,i) for (i,n) in enumerate(e[0] for e in l))
idx = d[53]

2
Якщо ми ігноруємо те, про що насправді просив ОП, я думаю, що ваша початкова відповідь є найкращою відповіддю на тему "Як шукати у списку кортежів на Python"
Рік Вестера,

Ваша перша відповідь була корисною для моїх цілей. Можливо, краще використовувати .get (), однак, якщо предмета немає в дикті. l = [(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")] val = dict(l).get(53)
user1503941

12

Хм ... ну, найпростіший спосіб, який спадає на думку - це перетворити його на дикт

d = dict(thelist)

та доступ d[53].

EDIT : На жаль, неправильно прочитайте своє запитання вперше. Здається, ви насправді хочете отримати індекс, де зберігається дане число. У такому випадку спробуйте

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

замість простого старого dictперетворення. Тоді d[53]було б 2.


6

Припустивши список може бути довгим і числа можуть повторюватися, розглянути питання про використання SortedList типу з модуля Python sortedcontainers . Тип SortedList автоматично підтримуватиме кортежі в порядку за номером і дозволяє здійснювати швидкий пошук.

Наприклад:

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]

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

Якщо є повторювані номери з різними рядками, то вам потрібно зробити ще один крок:

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

results = sl[index:end]

Поділивши дріб на 54, ми знайдемо кінцевий індекс для нашого зрізу. Це буде значно швидше у довгих списках порівняно з прийнятою відповіддю.



-1

[k для k, v в l, якщо v == ' деліція ']

ось l - список кортежів - [(1, "juca"), (22, "james"), (53, "xuxa"), (44, "delicia")]

І замість того, щоб перетворити це на дикт, ми використовуємо llist розуміння.

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


Так, авжеж. Дякую @cosmoonot.
Мантей Сінгх,

ось l - список кортежів - [(1, "juca"), (22, "james"), (53, "xuxa"), (44, "delicia")] І замість того, щоб перетворити його на dict, ми використовуємо llist розуміння. ` Ключ в ключ, значення в списку, де значення = Delicia `
Mantej Singh
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.