Encuentre el índice de un dict dentro de una lista, haciendo coincidir el valor del dict

130

Tengo una lista de dictados:

list = [{'id':'1234','name':'Jason'},
        {'id':'2345','name':'Tom'},
        {'id':'3456','name':'Art'}]

¿Cómo puedo encontrar eficientemente la posición del índice [0], [1] o [2] haciendo coincidir el nombre = 'Tom'?

Si se tratara de una lista unidimensional, podría hacer list.index () pero no estoy seguro de cómo proceder buscando los valores de los dictados dentro de la lista.

entrampar
fuente
66
"lista" es el constructor de la lista, es mejor que elija otro nombre para una lista (incluso en un ejemplo). ¿Y cuál debería ser la respuesta si no se encuentra ningún elemento? plantear una excepción? volver Ninguno?
tokland
77
Si usted va a necesitar mucho esto, utilizar una estructura de datos más apropiado (tal vez { 'Jason': {'id': '1234'}, 'Tom': {'id': '1245'}, ...}?)
3
@delnan ¡Porque esa es una receta para el desastre! En todo caso, debería ser {'1234': {'name': 'Jason'}, ...}. No es que eso ayude a este caso de uso.
OJFord

Respuestas:

145
tom_index = next((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)
# 1

Si necesita buscar repetidamente desde el nombre, debe indexarlos por nombre (usando un diccionario), de esta forma las operaciones de obtención serían O (1) vez. Una idea:

def build_dict(seq, key):
    return dict((d[key], dict(d, index=index)) for (index, d) in enumerate(seq))

info_by_name = build_dict(lst, key="name")
tom_info = info_by_name.get("Tom")
# {'index': 1, 'id': '2345', 'name': 'Tom'}
tokland
fuente
2
En mi humilde opinión esto no es tan legible o Pythonic es la respuesta de @ Emile. Debido a que la intención no es realmente crear un generador (y usarlo next()para esto me parece extraño), el objetivo es solo obtener el índice. Además, esto genera StopIteration, mientras que el lst.index()método Python genera ValueError.
Ben Hoyt
@benhoyt: tampoco me gusta la excepción StopIteration, pero si bien puede cambiar el valor predeterminado de next (), la excepción que genera es fija. La pitonicidad es algo subjetiva, por lo que no la cuestionaré, probablemente un bucle for es más pitónico. Por otro lado, algunas personas alias next () para first (), y eso definitivamente suena mejor: primero (index for (index, d) en ...).
tokland
first()suena mejor Siempre puede intentar / excepto StopIteration y aumentar ValueError para que la persona que llama tenga coherencia. Alternativamente, establezca next()el valor predeterminado en -1.
Ben Hoyt
1
@ gdw2: me sale SyntaxError: Generator expression must be parenthesized if not sole argumental hacer eso.
avoliva
2
@avoliva agrega un paréntesis alrededor del siguiente como siguenext((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)
HussienK
45

Una versión simple y legible es

def find(lst, key, value):
    for i, dic in enumerate(lst):
        if dic[key] == value:
            return i
    return -1
Emile
fuente
8
Esto parece el más legible y pitónico. También imita el comportamiento de str.find()bien. También puede llamarlo index()y aumentar un en ValueErrorlugar de devolver -1 si eso es preferible.
Ben Hoyt
66
De acuerdo: al devolver -1 cuando no se encuentra ninguna coincidencia, siempre obtendrá el último dict en la lista, que probablemente no sea lo que desea. Es mejor devolver None y verificar la existencia de una coincidencia en el código de llamada.
Shacker
9

No será eficiente, ya que debe recorrer la lista comprobando cada elemento (O (n)). Si quieres eficiencia, puedes usar dict of dicts . En cuanto a la pregunta, aquí hay una forma posible de encontrarla (aunque, si desea apegarse a esta estructura de datos, en realidad es más eficiente usar un generador como Brent Newey ha escrito en los comentarios; ver también la respuesta de tokland):

>>> L = [{'id':'1234','name':'Jason'},
...         {'id':'2345','name':'Tom'},
...         {'id':'3456','name':'Art'}]
>>> [i for i,_ in enumerate(L) if _['name'] == 'Tom'][0]
1
aeter
fuente
1
Puede obtener la eficiencia que desea utilizando un generador. Ver la respuesta de tokland.
Brent Newey
2
@Brent Newey: El generador no cambia el hecho de que tienes que recorrer toda la lista, haciendo la búsqueda O (n) como afirmaciones más antiguas ... Dependiendo de cuánto tiempo sea esa lista, la diferencia entre usar un generador y usar un bucle o lo que podría ser despreciable, wheras la diferencia entre usar un diccionario vs utilizando una lista no podría
Dirk
@Brent: Tiene razón, pero ¿puede superar una búsqueda O (1) en un diccionario, además si el elemento buscado está al final de la lista?
después del
1
@Dirk La siguiente llamada () en el generador se detiene cuando se encuentra una coincidencia, por lo tanto, no tiene que recorrer toda la lista.
Brent Newey
@aeter Haces un punto justo. Me refería a poder parar cuando se encuentra una coincidencia.
Brent Newey
2

Aquí hay una función que encuentra la posición de índice del diccionario si existe.

dicts = [{'id':'1234','name':'Jason'},
         {'id':'2345','name':'Tom'},
         {'id':'3456','name':'Art'}]

def find_index(dicts, key, value):
    class Null: pass
    for i, d in enumerate(dicts):
        if d.get(key, Null) == value:
            return i
    else:
        raise ValueError('no dict with the key and value combination found')

print find_index(dicts, 'name', 'Tom')
# 1
find_index(dicts, 'name', 'Ensnare')
# ValueError: no dict with the key and value combination found
Martineau
fuente
2

Parece más lógico usar un combo de filtro / índice:

names=[{}, {'name': 'Tom'},{'name': 'Tony'}]
names.index(filter(lambda n: n.get('name') == 'Tom', names)[0])
1

Y si crees que podría haber múltiples coincidencias:

[names.index(n) for item in filter(lambda n: n.get('name') == 'Tom', names)]
[1]
michael salmon
fuente
2

La respuesta ofrecida por @faham es agradable, pero no devuelve el índice al diccionario que contiene el valor. En su lugar, devuelve el diccionario en sí. Aquí hay una manera simple de obtener: Una lista de índices uno o más si hay más de uno, o una lista vacía si no hay ninguno:

list = [{'id':'1234','name':'Jason'},
        {'id':'2345','name':'Tom'},
        {'id':'3456','name':'Art'}]

[i for i, d in enumerate(list) if 'Tom' in d.values()]

Salida:

>>> [1]

Lo que me gusta de este enfoque es que con una simple edición puede obtener una lista de los índices y los diccionarios como tuplas. Este es el problema que necesitaba resolver y encontré estas respuestas. A continuación, agregué un valor duplicado en un diccionario diferente para mostrar cómo funciona:

list = [{'id':'1234','name':'Jason'},
        {'id':'2345','name':'Tom'},
        {'id':'3456','name':'Art'},
        {'id':'4567','name':'Tom'}]

[(i, d) for i, d in enumerate(list) if 'Tom' in d.values()]

Salida:

>>> [(1, {'id': '2345', 'name': 'Tom'}), (3, {'id': '4567', 'name': 'Tom'})]

Esta solución encuentra todos los diccionarios que contienen 'Tom' en cualquiera de sus valores.

stanely
fuente
1

¿¡Un trazador de líneas!?

elm = ([i for i in mylist if i['name'] == 'Tom'] or [None])[0]
faham
fuente
0

Para un iterable dado, more_itertools.locateproduce posiciones de elementos que satisfacen un predicado.

import more_itertools as mit


iterable = [
    {"id": "1234", "name": "Jason"},
    {"id": "2345", "name": "Tom"},
    {"id": "3456", "name": "Art"}
]

list(mit.locate(iterable, pred=lambda d: d["name"] == "Tom"))
# [1]

more_itertoolses una biblioteca de terceros que implementa recetas de herramientas iterto entre otras herramientas útiles.

pylang
fuente
0
def search(itemID,list):
     return[i for i in list if i.itemID==itemID]
Rohan Kumara
fuente