Как искать в списке кортежей в Python


91

Итак, у меня есть список таких кортежей:

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

Мне нужен этот список для кортежа, числовое значение которого чему-то равно.

Так что, если я это сделаю, search(53)он вернет значение индекса2

Есть простой способ сделать это?

Ответы:


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

69
Не могли бы вы объяснить?
schatten

17
Объяснение словами: для каждого i, v в нумерованном списке L (что делает i позицией элемента в перечисляемом списке и v исходным кортежем) проверьте, равен ли первый элемент кортежа 53, если да, добавьте результат кода перед «for» к вновь созданному списку, здесь: i. Это также может быть my_function (i, v) или еще одно понимание списка. Поскольку в вашем списке кортежей есть только один кортеж с 53 в качестве первого значения, вы получите список с одним элементом.
djangonaut

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

50

tl; dr

Выражение генератора , вероятно , является наиболее производительным и простым решением вашей проблемы:

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()для получения первого элемента из нашего генератора.

Давайте посмотрим, как эти методы по-разному работают с некоторыми большими наборами данных. Это большие списки, состоящие из 10 000 000 + 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}

КАКИЕ?! Лучший случай разрушает понимание списка, но я не ожидал, что наш худший случай превзойдет понимание списка в такой степени. Как так? Честно говоря, без дальнейших исследований я мог только предполагать.

Отнеситесь ко всему этому с недоверием, я не проводил здесь какого-либо надежного профилирования, а просто проводил очень простое тестирование. Этого должно быть достаточно, чтобы понять, что выражение генератора более производительно для этого типа поиска по списку.

Обратите внимание, что это все базовый встроенный питон. Нам не нужно ничего импортировать или использовать какие-либо библиотеки.

Впервые я увидел эту технику поиска на курсе Udacity cs212 с Питером Норвигом.


2
интересно, я протестировал и нашел его действительно быстрым
Grijesh Chauhan

3
Это должен быть принятый ответ. Выражения генератора не материализуют всю выходную последовательность при запуске, а скорее оценивают итератор, который выдает один элемент за раз из выражения.
BoltzmannBrain

2
Это здорово, в моем случае намного быстрее, чем составление списка, спасибо!
mindm49907


29

Ваши кортежи в основном представляют собой пары ключ-значение - python dict--so:

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
Если мы проигнорируем то, что на самом деле просил OP, я думаю, ваш первоначальный ответ - лучший ответ на вопрос «Как искать в списке кортежей в Python»
Рик Вестера

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

12

Хм ... ну, простой способ, который приходит в голову, - это преобразовать его в диктовку

d = dict(thelist)

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

РЕДАКТИРОВАТЬ : Ой, неправильно прочитал свой вопрос в первый раз. Похоже, вы действительно хотите получить индекс, в котором хранится данное число. В таком случае попробуйте

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, мы найдем конечный индекс для нашего среза. Это будет значительно быстрее для длинных списков по сравнению с принятым ответом.



-2

[k вместо k, v в l, если v == ' delicia ']

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

И вместо того, чтобы преобразовывать его в dict, мы используем понимание llist.

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


Да, конечно. Спасибо @cosmoonot.
Mantej Singh

здесь 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.