Cómo buscar una lista de tuplas en Python

90

Entonces tengo una lista de tuplas como esta:

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

Quiero esta lista para una tupla cuyo valor numérico sea igual a algo.

De modo que si lo hago search(53), devolverá el valor de índice de2

¿Hay una forma fácil de hacer esto?

hdx
fuente

Respuestas:

94
[i for i, v in enumerate(L) if v[0] == 53]
Ignacio Vázquez-Abrams
fuente
68
¿Podría explicarme por favor?
schatten el
17
Explicado en palabras: Para cada i, v en una lista enumerada de L (que hace que i sea la posición del elemento en la lista enumerada yv la tupla original) verifique si el primer elemento de la tupla es 53, si es así, agregue el resultado del código antes de 'para' a una lista recién creada, aquí: i. También podría ser my_function (i, v) u otra lista de comprensión más. Dado que su lista de tuplas solo tiene una tupla con 53 como primer valor, obtendrá una lista con un elemento.
djangonaut
6
Solo agregaría [i para i, v en enumerate (L) si v [0] == 53] .pop () para tener un valor int.
alemol
49

Puedes usar un lista de comprensión :

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

tl; dr

Una expresión generadora es probablemente la solución más eficaz y sencilla a su 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

Explicación

Hay varias respuestas que brindan una solución simple a esta pregunta con listas por comprensión. Si bien estas respuestas son perfectamente correctas, no son óptimas. Dependiendo de su caso de uso, puede haber beneficios significativos al realizar algunas modificaciones simples.

El principal problema que veo con el uso de una lista de comprensión para este caso de uso es que se procesará la lista completa , aunque solo desea encontrar 1 elemento .

Python proporciona una construcción simple que es ideal aquí. Se llama expresión generadora . Aquí hay un ejemplo:

# 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 este método funcione básicamente de la misma manera que las listas por comprensión de nuestro ejemplo trivial, pero ¿qué pasa si estamos trabajando con un conjunto de datos más grande? Ahí es donde entra en juego la ventaja de utilizar el método del generador. En lugar de construir una nueva lista, usaremos su lista existente como nuestro iterable, y usaremos next()para obtener el primer elemento de nuestro generador.

Veamos cómo estos métodos funcionan de manera diferente en algunos conjuntos de datos más grandes. Estas son listas grandes, hechas de 10000000 + 1 elementos, con nuestro objetivo al principio (mejor) o al final (peor). Podemos verificar que ambas listas funcionarán por igual usando la siguiente comprensión de listas:

Lista de comprensiones

"Peor de los casos"

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}

"Mejor 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}

Expresiones generadoras

Aquí está mi hipótesis para los generadores: veremos que los generadores funcionarán significativamente mejor en el mejor de los casos, pero de manera similar en el peor de los casos. Esta ganancia de rendimiento se debe principalmente al hecho de que el generador se evalúa de manera perezosa, lo que significa que solo calculará lo que se requiere para producir un valor.

Peor de los casos

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

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

¡¿QUÉ?! El mejor caso estalla las comprensiones de la lista, pero no esperaba que nuestro peor caso superara las comprensiones de la lista hasta tal punto. ¿Como es eso? Francamente, solo podría especular sin más investigación.

Tome todo esto con un grano de sal, no he ejecutado ningún perfil sólido aquí, solo algunas pruebas muy básicas. Esto debería ser suficiente para apreciar que una expresión generadora es más eficaz para este tipo de búsqueda de listas.

Tenga en cuenta que todo esto es Python básico integrado. No necesitamos importar nada ni utilizar bibliotecas.

Vi por primera vez esta técnica de búsqueda en el curso Udacity cs212 con Peter Norvig.

Jon Surrell
fuente
2
interesante, probé y encontré que es realmente rápido
Grijesh Chauhan
3
Esta debería ser la respuesta aceptada. Las expresiones del generador no materializan toda la secuencia de salida cuando se ejecutan, sino que se evalúan en un iterador que produce un elemento a la vez de la expresión.
BoltzmannBrain
2
Esto es genial, mucho más rápido que la comprensión de una lista en mi caso, ¡gracias!
mindm49907
29

Sus tuplas son básicamente pares clave-valor, una pitón, dictentonces:

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

Editar - ajá, dices que quieres el valor de índice de (53, "xuxa"). Si esto es realmente lo que desea, tendrá que recorrer la lista original o quizás hacer un diccionario más complicado:

d = dict((n,i) for (i,n) in enumerate(e[0] for e in l))
idx = d[53]
Andrew Jaffe
fuente
2
Si ignoramos lo que realmente pidió el OP, creo que su respuesta inicial es la mejor respuesta a "Cómo buscar una lista de tuplas en Python"
Rick Westera
Tu primera respuesta fue útil para mis propósitos. Quizás sea mejor usar .get (), en caso de que el elemento no esté en el dict. l = [(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")] val = dict(l).get(53)
user1503941
12

Hmm ... bueno, la forma sencilla que me viene a la mente es convertirlo en un dict

d = dict(thelist)

y acceso d[53].

EDITAR : Vaya, leyó mal su pregunta la primera vez. Parece que realmente desea obtener el índice donde se almacena un número determinado. En ese caso, intente

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

en lugar de una simple dictconversión antigua . Entonces d[53]sería 2.

David Z
fuente
6

Suponiendo que la lista puede ser larga y los números puede repetir, considere el uso de la SortedList tipo del módulo de Python sortedcontainers . El tipo SortedList mantendrá automáticamente las tuplas en orden por número y permitirá una búsqueda rápida.

Por ejemplo:

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]

Esto funcionará mucho más rápido que la sugerencia de comprensión de la lista al hacer una búsqueda binaria. La sugerencia del diccionario será aún más rápida, pero no funcionará si pudiera haber números duplicados con diferentes cadenas.

Si hay números duplicados con diferentes cadenas, debe dar un paso más:

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

results = sl[index:end]

Al bisecar para 54, encontraremos el índice final de nuestro segmento. Esto será significativamente más rápido en listas largas en comparación con la respuesta aceptada.

GrantJ
fuente
1

Solo de otra forma.

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

[k para k, v en l si v == ' delicia ']

aquí está la lista de tuplas - [(1, "juca"), (22, "james"), (53, "xuxa"), (44, "delicia")]

Y en lugar de convertirlo en un diccionario, usamos la comprensión de listas.

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

Mantej Singh
fuente
Si seguro. Gracias @cosmoonot.
Mantej Singh
aquí está la lista de tuplas - [(1, "juca"), (22, "james"), (53, "xuxa"), (44, "delicia")] Y en lugar de convertirlo en un dict, estamos usando la comprensión de la lista. ` Clave en la clave, valor en la lista, donde el valor = Delicia '
Mantej Singh