Como pesquisar uma lista de tuplas em Python

90

Portanto, tenho uma lista de tuplas como esta:

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

Eu quero essa lista para uma tupla cujo valor numérico é igual a alguma coisa.

Portanto, se eu fizer search(53)isso, o valor do índice de2

Existe uma maneira fácil de fazer isso?

hdx
fonte

Respostas:

94
[i for i, v in enumerate(L) if v[0] == 53]
Ignacio Vazquez-Abrams
fonte
68
Você poderia explicar por favor?
schatten
17
Explicado em palavras: para cada i, v em uma lista enumerada de L (que torna i a posição do elemento na lista enumerada ev a tupla original) verifique se o primeiro elemento da tupla é 53, em caso afirmativo, anexe o resultado do código antes de 'para' para uma lista recém-criada, aqui: i. Também pode ser my_function (i, v) ou ainda outra compreensão de lista. Como sua lista de tuplas tem apenas uma tupla com 53 como primeiro valor, você obterá uma lista com um elemento.
Djangonaut
6
Eu apenas adicionaria [i for i, v in enumerate (L) se v [0] == 53] .pop () para ter o valor int.
alemol
49

Você pode usar uma compreensão de lista :

>>> 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
fonte
47

tl; dr

Uma expressão geradora é provavelmente a solução mais simples e eficiente para o seu problema:

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

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

Explicação

Existem várias respostas que fornecem uma solução simples para essa questão com compreensões de lista. Embora essas respostas sejam perfeitamente corretas, elas não são as ideais. Dependendo do seu caso de uso, pode haver benefícios significativos em fazer algumas modificações simples.

O principal problema que vejo ao usar uma compreensão de lista para este caso de uso é que a lista inteira será processada, embora você queira encontrar apenas 1 elemento .

Python fornece uma construção simples que é ideal aqui. É chamada de expressão geradora . Aqui está um exemplo:

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

Podemos esperar que esse método execute basicamente o mesmo que as compreensões de lista em nosso exemplo trivial, mas e se estivermos trabalhando com um conjunto de dados maior? É aí que entra a vantagem de usar o método gerador. Em vez de construir uma nova lista, usaremos sua lista existente como nosso iterável e usaremos next()para obter o primeiro item de nosso gerador.

Vamos ver como esses métodos funcionam de maneira diferente em alguns conjuntos de dados maiores. Essas são listas grandes, feitas de 10000000 + 1 elementos, com nosso alvo no início (melhor) ou no final (pior). Podemos verificar que ambas as listas terão um desempenho igual usando a seguinte compreensão de lista:

Compreensões de lista

"Pior caso"

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}

"Melhor caso"

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}

Expressões geradoras

Aqui está minha hipótese para geradores: veremos que os geradores terão um desempenho significativamente melhor no melhor caso, mas da mesma forma no pior caso. Esse ganho de desempenho se deve principalmente ao fato de que o gerador é avaliado vagarosamente, o que significa que ele computará apenas o que é necessário para gerar um valor.

Pior caso

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

Melhor caso

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}

O QUE?! O melhor caso destrói as compreensões de lista, mas eu não esperava que nosso pior caso superasse as compreensões de lista a tal ponto. Como é isso? Francamente, eu só poderia especular sem mais pesquisas.

Considere tudo isso com cautela, não executei nenhum perfil robusto aqui, apenas alguns testes básicos. Isso deve ser suficiente para avaliar que uma expressão geradora tem melhor desempenho para esse tipo de pesquisa de lista.

Observe que tudo isso é python básico e integrado. Não precisamos importar nada ou usar qualquer biblioteca.

Eu vi essa técnica pela primeira vez para pesquisa no curso Udacity cs212 com Peter Norvig.

Jon Surrell
fonte
2
interessante, eu testei e achei muito rápido
Grijesh Chauhan
3
Esta deve ser a resposta aceita. Expressões geradoras não materializam toda a sequência de saída quando são executadas, mas sim avaliam como um iterador que produz um item por vez a partir da expressão.
BoltzmannBrain
2
Isso é ótimo, muito mais rápido do que a compreensão de uma lista no meu caso, obrigado!
mindm49907
29

Suas tuplas são basicamente pares de valores-chave - um python - dictentão:

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

Editar - aha, você diz que deseja o valor do índice de (53, "xuxa"). Se isso é realmente o que você deseja, você terá que iterar pela lista original ou talvez fazer um dicionário mais complicado:

d = dict((n,i) for (i,n) in enumerate(e[0] for e in l))
idx = d[53]
Andrew Jaffe
fonte
2
Se ignorarmos o que o OP realmente pediu, acho que sua resposta inicial é a melhor resposta para "Como pesquisar uma lista de tuplas em Python"
Rick Westera
Sua primeira resposta foi útil para meus objetivos. Talvez seja melhor usar .get (), caso o item não esteja no dicionário. l = [(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")] val = dict(l).get(53)
user1503941
12

Hmm ... bem, a maneira simples que vem à mente é convertê-lo em um dicionário

d = dict(thelist)

e acesso d[53].

EDIT : Oops, interpretou mal sua pergunta na primeira vez. Parece que você realmente deseja obter o índice onde um determinado número está armazenado. Nesse caso, tente

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

em vez de uma simples dictconversão antiga . Então d[53]seriam 2.

David Z
fonte
6

Supondo que a lista possa ser longa e os números possam se repetir, considere usar o tipo SortedList do módulo Python SortedContainers . O tipo SortedList manterá automaticamente as tuplas em ordem por número e permitirá uma pesquisa rápida.

Por exemplo:

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]

Isso funcionará muito mais rápido do que a sugestão de compreensão da lista fazendo uma pesquisa binária. A sugestão de dicionário será mais rápida ainda, mas não funcionará se houver números duplicados com strings diferentes.

Se houver números duplicados com strings diferentes, você precisará realizar mais uma etapa:

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

results = sl[index:end]

Ao dividir ao meio para 54, encontraremos o índice final para nossa fatia. Isso será significativamente mais rápido em listas longas em comparação com a resposta aceita.

GrantJ
fonte
1

Apenas outra maneira.

zip(*a)[0].index(53)
RussW
fonte
-1

[k para k, v em l if v == ' delicia ']

aqui l é a lista de tuplas - [(1, "juca"), (22, "james"), (53, "xuxa"), (44, "delicia")]

E em vez de convertê-lo em um dicionário, estamos usando a compreensão de lista.

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

Mantej Singh
fonte
Sim claro. Obrigado @cosmoonot.
Mantej Singh
aqui l é a lista de tuplas - [(1, "juca"), (22, "james"), (53, "xuxa"), (44, "delicia")] E em vez de convertê-la em um dict, estamos usando a compreensão da lista. ` Key em Key, Valor na lista, onde o valor = Delicia `
Mantej Singh