Как искать список кортежей в Python - PullRequest
88 голосов
/ 27 мая 2010

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

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

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

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

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

Ответы [ 8 ]

85 голосов
/ 27 мая 2010
[i for i, v in enumerate(L) if v[0] == 53]
48 голосов
/ 27 мая 2010

Вы можете использовать список понимания :

>>> 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
42 голосов
/ 02 июня 2012

ТЛ; др

A генератор выражений , пожалуй, самое эффективное и простое решение вашей проблемы:

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 с Питером Норвигом.

26 голосов
/ 27 мая 2010

Ваши кортежи в основном пары ключ-значение - питон dict - так:

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

Edit - ага, вы говорите, что хотите значение индекса (53, "xuxa"). Если это действительно , что вы хотите, вам придется перебирать исходный список или, возможно, создать более сложный словарь:

d = dict((n,i) for (i,n) in enumerate(e[0] for e in l))
idx = d[53]
12 голосов
/ 27 мая 2010

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

d = dict(thelist)

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

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

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

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

6 голосов
/ 11 апреля 2014

Если список может быть длинным, а числа могут повторяться, рассмотрите возможность использования типа 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 голос
/ 23 июля 2013

Просто по-другому.

zip(*a)[0].index(53)
0 голосов
/ 25 апреля 2017

[k для k, v в l, если v == ' delicia ']

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...