Encontre o índice de um ditado em uma lista, correspondendo ao valor do ditado


130

Eu tenho uma lista de ditados:

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

Como posso encontrar com eficiência a posição do índice [0], [1] ou [2] combinando com name = 'Tom'?

Se essa fosse uma lista unidimensional, eu poderia fazer list.index (), mas não tenho certeza de como proceder pesquisando os valores dos dicts na lista.


6
"list" é o construtor da lista, é melhor escolher outro nome para uma lista (mesmo em um exemplo). E qual deve ser a resposta se nenhum elemento for encontrado? criar uma exceção? retornar Nenhum?
tokland

7
Se você precisar muito disso, use uma estrutura de dados mais apropriada (talvez { 'Jason': {'id': '1234'}, 'Tom': {'id': '1245'}, ...}?) #

3
@ delnan Porque essa é uma receita para o desastre! Se alguma coisa, deveria ser {'1234': {'name': 'Jason'}, ...}. Não que isso ajude esse caso de uso.
OJFord 9/09/16

Respostas:


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

Se você precisar buscar repetidamente o nome, indexe-os por nome (usando um dicionário), dessa maneira as operações get serão O (1) time. Uma ideia:

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

2
IMHO isso não é tão legível ou Pythonic é a resposta de @ Emile. Como a intenção não é realmente criar um gerador (e usar next()isso me parece estranho), o objetivo é apenas obter o índice. Além disso, isso gera StopIteration, enquanto o lst.index()método Python gera ValueError.
Ben Hoyt

@ benhoyt: Eu também não gosto da exceção StopIteration, mas enquanto você pode alterar o valor padrão do next (), a exceção que ele gera é corrigida. A pitonicidade é um tanto subjetiva, então não vou contestar, provavelmente um loop for é mais pitônico. Por outro lado, algumas pessoas apelidam next () para first (), e isso definitivamente soa melhor: first (index for (index, d) in ...).
tokland

first()soa melhor. Você sempre pode tentar / exceto o StopIteration e aumentar ValueError para que o chamador tenha consistência. Como alternativa, defina next()o padrão como -1.
Ben Hoyt

1
@ gdw2: recebo SyntaxError: Generator expression must be parenthesized if not sole argumentao fazer isso.
avoliva

2
@avoliva adicione um parêntese ao redor da seguinte maneiranext((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)
HussienK

45

Uma versão simples e legível é

def find(lst, key, value):
    for i, dic in enumerate(lst):
        if dic[key] == value:
            return i
    return -1

8
Parece o mais legível e pitonico. Também imita o comportamento de str.find()bem. Você também pode chamá-lo index()e aumentar um em ValueErrorvez de retornar -1, se preferível.
Ben Hoyt

6
Concordo - retornando -1 quando nenhuma correspondência for encontrada, você sempre obterá o último ditado da lista, o que provavelmente não é o que você deseja. Melhor retornar None e verificar a existência de uma correspondência no código de chamada.
shacker

9

Não será eficiente, pois você precisa percorrer a lista verificando todos os itens (O (n)). Se você quer eficiência, pode usar o dict of dicts . Sobre a questão, aqui está uma maneira possível de encontrá-lo (porém, se você deseja manter essa estrutura de dados, é realmente mais eficiente usar um gerador, como Brent Newey escreveu nos comentários; veja também a resposta do 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

1
Você pode obter a eficiência desejada usando um gerador. Veja a resposta do tokland.
Brent Newey

2
@Brent Newey: O gerador não muda o fato de que você precisa percorrer a lista inteira, fazendo a pesquisa O (n) conforme afirmam outras ... Dependendo de quanto tempo é essa lista, a diferença entre usar um gerador e usar um loop for ou o que poderia ser neglible, wheras a diferença entre usar um dicionário vs. usando uma lista não pode
Dirk

@Brent: Você está certo, mas ele pode bater uma pesquisa O (1) em um dicionário, além disso, se o item pesquisado estiver no final da lista?
aeter

1
@Dirk A próxima chamada () no gerador para quando uma correspondência é encontrada, portanto, não é necessário percorrer a lista inteira.
Brent Newey

@ aeter Você faz um argumento justo. Eu estava me referindo a ser capaz de parar quando uma correspondência é encontrada.
Brent Newey

2

Aqui está uma função que localiza a posição de índice do dicionário, se existir.

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

2

Parece mais lógico usar uma combinação de filtro / índice:

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

E se você acha que pode haver várias correspondências:

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

2

A resposta oferecida por @faham é uma ótima linha, mas não retorna o índice para o dicionário que contém o valor. Em vez disso, ele retorna o próprio dicionário. Aqui está uma maneira simples de obter: Uma lista de índices, um ou mais, se houver mais de um, ou uma lista vazia, se não houver:

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()]

Resultado:

>>> [1]

O que eu gosto nessa abordagem é que, com uma edição simples, você pode obter uma lista dos índices e dos dicionários como tuplas. Este é o problema que eu precisava resolver e encontrei essas respostas. A seguir, adicionei um valor duplicado em um dicionário diferente para mostrar como ele 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()]

Resultado:

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

Esta solução localiza todos os dicionários que contêm 'Tom' em qualquer um de seus valores.


1

Um forro !?

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

0

Para um dado iterável, more_itertools.locategera posições de itens que satisfazem um 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_itertoolsé uma biblioteca de terceiros que implementa receitas de ferramentas entre outras ferramentas úteis.


0
def search(itemID,list):
     return[i for i in list if i.itemID==itemID]
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.