Wyszukiwanie słowników w języku Python

449

Załóżmy, że mam to:

[
{"name": "Tom", "age": 10},
{"name": "Mark", "age": 5},
{"name": "Pam", "age": 7}
]

i wyszukując „Pam” jako nazwę, chcę pobrać powiązany słownik: {name: "Pam", age: 7}

Jak to osiągnąć?

Hellnar
źródło

Odpowiedzi:

512

Możesz użyć wyrażenia generatora :

>>> dicts = [
...     { "name": "Tom", "age": 10 },
...     { "name": "Mark", "age": 5 },
...     { "name": "Pam", "age": 7 },
...     { "name": "Dick", "age": 12 }
... ]

>>> next(item for item in dicts if item["name"] == "Pam")
{'age': 7, 'name': 'Pam'}

Jeśli musisz poradzić sobie z brakiem elementu, możesz zrobić to, co sugerował użytkownik Matt w swoim komentarzu, i podać wartość domyślną, używając nieco innego interfejsu API:

next((item for item in dicts if item["name"] == "Pam"), None)

Aby znaleźć indeks elementu, a nie sam element, możesz wyliczyć () listę:

next((i for i, item in enumerate(dicts) if item["name"] == "Pam"), None)
Frédéric Hamidi
źródło
230
Aby zaoszczędzić trochę czasu innym, jeśli potrzebujesz wartości domyślnej w przypadku „Pam”, po prostu nie ma jej na liście: next ((pozycja dla pozycji w nagraniach, jeśli item [„name”] == „Pam”) , Brak)
Matt
1
Co [item for item in dicts if item["name"] == "Pam"][0]?
Moberg,
3
@Mergerg, nadal jest to lista, więc będzie iterować całą sekwencję wejściową, niezależnie od pozycji pasującego elementu.
Frédéric Hamidi
7
Powoduje to błąd stopiteracji, jeśli klucz nie występuje w słowniku
Kishan
3
@Siemkowski: następnie dodać enumerate()do wygenerowania systemem indeksu: next(i for i, item in enumerate(dicts) if item["name"] == "Pam").
Martijn Pieters
218

To wydaje mi się najbardziej pytonicznym sposobem:

people = [
{'name': "Tom", 'age': 10},
{'name': "Mark", 'age': 5},
{'name': "Pam", 'age': 7}
]

filter(lambda person: person['name'] == 'Pam', people)

wynik (zwrócony jako lista w Pythonie 2):

[{'age': 7, 'name': 'Pam'}]

Uwaga: W Pythonie 3 zwracany jest obiekt filtru. Tak więc rozwiązaniem python3 byłoby:

list(filter(lambda person: person['name'] == 'Pam', people))
PaoloC
źródło
14
Warto zauważyć, że ta odpowiedź zwraca listę wszystkich dopasowań dla „Pam” u ludzi, alternatywnie moglibyśmy uzyskać listę wszystkich osób, które nie są „Pam”, zmieniając operator porównania na! =. +1
Onema
2
Warto również wspomnieć, że wynikiem jest obiekt filtru, a nie lista - jeśli chcesz użyć takich rzeczy len(), musisz najpierw wywołać list()wynik. Lub: stackoverflow.com/questions/19182188/…
wasabigeek
@wasabigeek tak mówi mój Python 2.7: people = [{'name': "Tom", 'age': 10}, {'name': "Mark", "age ': 5}, {' name ': „Pam”, „wiek”: 7}] r = filtr (osoba lambda: osoba [„imię”] == „Pam”, ludzie) lista typów (r) Tak rjestlist
PaoloC
1
Zrozumienie
2
Zdobądź pierwszy mecz:next(filter(lambda x: x['name'] == 'Pam', dicts))
xgMz
60

@ Frédéric Hamidi odpowiedź jest świetna. W Pythonie 3.x .next()zmieniono nieco składnię . Zatem niewielka modyfikacja:

>>> dicts = [
     { "name": "Tom", "age": 10 },
     { "name": "Mark", "age": 5 },
     { "name": "Pam", "age": 7 },
     { "name": "Dick", "age": 12 }
 ]
>>> next(item for item in dicts if item["name"] == "Pam")
{'age': 7, 'name': 'Pam'}

Jak wspomniano w komentarzach @Matt, możesz dodać wartość domyślną jako taką:

>>> next((item for item in dicts if item["name"] == "Pam"), False)
{'name': 'Pam', 'age': 7}
>>> next((item for item in dicts if item["name"] == "Sam"), False)
False
>>>
Mike N.
źródło
1
To najlepsza odpowiedź dla Pythona 3.x. Jeśli potrzebujesz określonego elementu ze słownika, takiego jak wiek, możesz napisać: next ((item.get ('age')) dla elementu ze słownika, jeśli item ["name"] == "Pam"), False)
cwhisperer
47

Możesz użyć rozumienia listy :

def search(name, people):
    return [element for element in people if element['name'] == name]

źródło
4
Jest to miłe, ponieważ zwraca wszystkie dopasowania, jeśli jest ich więcej niż jeden. Nie do końca to pytanie, ale tego właśnie potrzebowałem! Dzięki!
user3303554,
Zauważ też, że to zwraca listę!
Abbas
34
people = [
{'name': "Tom", 'age': 10},
{'name': "Mark", 'age': 5},
{'name': "Pam", 'age': 7}
]

def search(name):
    for p in people:
        if p['name'] == name:
            return p

search("Pam")
satoru
źródło
Zwróci pierwszy słownik z listy o podanej nazwie.
Ricky Robinson
5
Żeby ta bardzo przydatna rutyna była nieco bardziej ogólna:def search(list, key, value): for item in list: if item[key] == value: return item
Jack James
30

Przetestowałem różne metody przeglądania listy słowników i zwracania słowników, w których klucz x ma określoną wartość.

Wyniki:

  • Szybkość: zrozumienie listy> wyrażenie generatora >> normalna iteracja listy >>> filtr.
  • Cała skala liniowa z liczbą dykt na liście (10-krotny rozmiar listy -> 10-krotny czas).
  • Liczba kluczy w słowniku nie wpływa znacząco na szybkość w przypadku dużych ilości (tysięcy) kluczy. Proszę zobaczyć ten wykres, który obliczyłem: https://imgur.com/a/quQzv (nazwy metod patrz poniżej).

Wszystkie testy wykonane w Pythonie 3.6 .4, W7x64.

from random import randint
from timeit import timeit


list_dicts = []
for _ in range(1000):     # number of dicts in the list
    dict_tmp = {}
    for i in range(10):   # number of keys for each dict
        dict_tmp[f"key{i}"] = randint(0,50)
    list_dicts.append( dict_tmp )



def a():
    # normal iteration over all elements
    for dict_ in list_dicts:
        if dict_["key3"] == 20:
            pass

def b():
    # use 'generator'
    for dict_ in (x for x in list_dicts if x["key3"] == 20):
        pass

def c():
    # use 'list'
    for dict_ in [x for x in list_dicts if x["key3"] == 20]:
        pass

def d():
    # use 'filter'
    for dict_ in filter(lambda x: x['key3'] == 20, list_dicts):
        pass

Wyniki:

1.7303 # normal list iteration 
1.3849 # generator expression 
1.3158 # list comprehension 
7.7848 # filter
użytkownik136036
źródło
Dodałem funkcję z (), która implementuje następną, jak wskazał Frédéric Hamidi powyżej. Oto wyniki z profilu Py.
Leon
10

Aby dodać tylko odrobinę do @ FrédéricHamidi.

Jeśli nie masz pewności, czy klucz znajduje się na liście nagrań, coś takiego pomoże:

next((item for item in dicts if item.get("name") and item["name"] == "Pam"), None)
Drazen Urch
źródło
lub po prostuitem.get("name") == "Pam"
Andreas Haferburg
10

Czy kiedykolwiek wypróbowałeś pakiet pand? Jest idealny do tego rodzaju zadań wyszukiwania i zoptymalizowany.

import pandas as pd

listOfDicts = [
{"name": "Tom", "age": 10},
{"name": "Mark", "age": 5},
{"name": "Pam", "age": 7}
]

# Create a data frame, keys are used as column headers.
# Dict items with the same key are entered into the same respective column.
df = pd.DataFrame(listOfDicts)

# The pandas dataframe allows you to pick out specific values like so:

df2 = df[ (df['name'] == 'Pam') & (df['age'] == 7) ]

# Alternate syntax, same thing

df2 = df[ (df.name == 'Pam') & (df.age == 7) ]

Poniżej dodałem trochę testów porównawczych, aby zilustrować szybsze czasy działania pand na większą skalę, tj. Ponad 100 000 wpisów:

setup_large = 'dicts = [];\
[dicts.extend(({ "name": "Tom", "age": 10 },{ "name": "Mark", "age": 5 },\
{ "name": "Pam", "age": 7 },{ "name": "Dick", "age": 12 })) for _ in range(25000)];\
from operator import itemgetter;import pandas as pd;\
df = pd.DataFrame(dicts);'

setup_small = 'dicts = [];\
dicts.extend(({ "name": "Tom", "age": 10 },{ "name": "Mark", "age": 5 },\
{ "name": "Pam", "age": 7 },{ "name": "Dick", "age": 12 }));\
from operator import itemgetter;import pandas as pd;\
df = pd.DataFrame(dicts);'

method1 = '[item for item in dicts if item["name"] == "Pam"]'
method2 = 'df[df["name"] == "Pam"]'

import timeit
t = timeit.Timer(method1, setup_small)
print('Small Method LC: ' + str(t.timeit(100)))
t = timeit.Timer(method2, setup_small)
print('Small Method Pandas: ' + str(t.timeit(100)))

t = timeit.Timer(method1, setup_large)
print('Large Method LC: ' + str(t.timeit(100)))
t = timeit.Timer(method2, setup_large)
print('Large Method Pandas: ' + str(t.timeit(100)))

#Small Method LC: 0.000191926956177
#Small Method Pandas: 0.044392824173
#Large Method LC: 1.98827004433
#Large Method Pandas: 0.324505090714
abby szloch
źródło
7

Jest to ogólny sposób wyszukiwania wartości na liście słowników:

def search_dictionaries(key, value, list_of_dictionaries):
    return [element for element in list_of_dictionaries if element[key] == value]
ipegasus
źródło
6
names = [{'name':'Tom', 'age': 10}, {'name': 'Mark', 'age': 5}, {'name': 'Pam', 'age': 7}]
resultlist = [d    for d in names     if d.get('name', '') == 'Pam']
first_result = resultlist[0]

To jest jeden sposób ...

Niclas Nilsson
źródło
1
Mógłbym zasugerować [d dla xw nazwach, jeśli d.get ('name', '') == 'Pam'] ... z wdziękiem obsługiwał wszelkie wpisy w „nazwach”, które nie miały klucza „nazwa”.
Jim Dennis
6

Po prostu używając rozumienia listy:

[i for i in dct if i['name'] == 'Pam'][0]

Przykładowy kod:

dct = [
    {'name': 'Tom', 'age': 10},
    {'name': 'Mark', 'age': 5},
    {'name': 'Pam', 'age': 7}
]

print([i for i in dct if i['name'] == 'Pam'][0])

> {'age': 7, 'name': 'Pam'}
Teoretyk
źródło
5

Możesz to osiągnąć za pomocą filtru i następnych metod w Pythonie.

filter Metoda filtruje podaną sekwencję i zwraca iterator. nextMetoda akceptuje iterator i zwraca następny element na liście.

Aby znaleźć element,

my_dict = [
    {"name": "Tom", "age": 10},
    {"name": "Mark", "age": 5},
    {"name": "Pam", "age": 7}
]

next(filter(lambda obj: obj.get('name') == 'Pam', my_dict), None)

a wynikiem jest

{'name': 'Pam', 'age': 7}

Uwaga: powyższy kod zwróci Noneincase, jeśli szukana nazwa nie zostanie znaleziona.

Manoj Kumar S.
źródło
Jest to o wiele wolniejsze niż rozumienie list.
AnupamChugh
4

Najpierw pomyślałem, że możesz rozważyć utworzenie słownika tych słowników ... jeśli, na przykład, będziesz go przeszukiwał więcej niż kilka razy.

Może to być jednak przedwczesna optymalizacja. Co byłoby nie tak z:

def get_records(key, store=dict()):
    '''Return a list of all records containing name==key from our store
    '''
    assert key is not None
    return [d for d in store if d['name']==key]
Jim Dennis
źródło
W rzeczywistości możesz mieć słownik o nazwie = Brak elementu; ale tak naprawdę nie działałoby to ze zrozumieniem tej listy i prawdopodobnie nie jest rozsądne, aby pozwolić na to w twoim magazynie danych.
Jim Dennis
1
aserty mogą być pomijane, jeśli tryb debugowania jest wyłączony.
bluppfisk
4
dicts=[
{"name": "Tom", "age": 10},
{"name": "Mark", "age": 5},
{"name": "Pam", "age": 7}
]

from collections import defaultdict
dicts_by_name=defaultdict(list)
for d in dicts:
    dicts_by_name[d['name']]=d

print dicts_by_name['Tom']

#output
#>>>
#{'age': 10, 'name': 'Tom'}
Robert King
źródło
3

Jednym prostym sposobem korzystania ze zrozumień listy jest, jeśli llista jest

l = [
{"name": "Tom", "age": 10},
{"name": "Mark", "age": 5},
{"name": "Pam", "age": 7}
]

następnie

[d['age'] for d in l if d['name']=='Tom']
CVG
źródło
2

Możesz spróbować:

''' lst: list of dictionaries '''
lst = [{"name": "Tom", "age": 10}, {"name": "Mark", "age": 5}, {"name": "Pam", "age": 7}]

search = raw_input("What name: ") #Input name that needs to be searched (say 'Pam')

print [ lst[i] for i in range(len(lst)) if(lst[i]["name"]==search) ][0] #Output
>>> {'age': 7, 'name': 'Pam'} 
Siddharth Satpathy
źródło
1

Oto porównanie za pomocą iteracji listy, za pomocą filtru + lambda lub refaktoryzacji (jeśli jest to konieczne lub ważne w przypadku) kodu do dyktowania, a nie listy dykt

import time

# Build list of dicts
list_of_dicts = list()
for i in range(100000):
    list_of_dicts.append({'id': i, 'name': 'Tom'})

# Build dict of dicts
dict_of_dicts = dict()
for i in range(100000):
    dict_of_dicts[i] = {'name': 'Tom'}


# Find the one with ID of 99

# 1. iterate through the list
lod_ts = time.time()
for elem in list_of_dicts:
    if elem['id'] == 99999:
        break
lod_tf = time.time()
lod_td = lod_tf - lod_ts

# 2. Use filter
f_ts = time.time()
x = filter(lambda k: k['id'] == 99999, list_of_dicts)
f_tf = time.time()
f_td = f_tf- f_ts

# 3. find it in dict of dicts
dod_ts = time.time()
x = dict_of_dicts[99999]
dod_tf = time.time()
dod_td = dod_tf - dod_ts


print 'List of Dictionries took: %s' % lod_td
print 'Using filter took: %s' % f_td
print 'Dict of Dicts took: %s' % dod_td

A wynik jest następujący:

List of Dictionries took: 0.0099310874939
Using filter took: 0.0121960639954
Dict of Dicts took: 4.05311584473e-06

Wniosek: Najwyraźniej posiadanie słownika słowników jest najskuteczniejszym sposobem wyszukiwania w tych przypadkach, w których wiesz, że będziesz wyszukiwał tylko według identyfikatora. co ciekawe, użycie filtru jest najwolniejszym rozwiązaniem.

Kőhalmy Zoltán
źródło
0

Musisz przejść przez wszystkie elementy listy. Nie ma skrótu!

Chyba że gdzieś indziej prowadzisz słownik nazw wskazujący na elementy listy, ale musisz zadbać o konsekwencje wyskakiwania elementu z listy.

jimifiki
źródło
W przypadku nieposortowanej listy i brakującego klucza to stwierdzenie jest poprawne, ale ogólnie nie. Jeśli wiadomo, że lista jest posortowana, nie trzeba powtarzać wszystkich elementów. Ponadto, jeśli trafiony zostanie pojedynczy rekord i wiesz, że klucze są unikalne lub wymagają tylko jednego elementu, iterację można zatrzymać, zwracając pojedynczy element.
user25064,
patrz odpowiedź @ user334856
Melih Yıldız
@ MelihYıldız „może nie byłem jasny w moim oświadczeniu. Korzystając ze zrozumienia listy user334856 w odpowiedzi stackoverflow.com/a/8653572/512225 przegląda całą listę. To potwierdza moje oświadczenie. Odpowiedź, którą polecasz, to inny sposób na powiedzenie tego, co napisałem.
jimifiki
0

Znalazłem ten wątek, gdy szukałem odpowiedzi na to samo pytanie. Chociaż zdaję sobie sprawę, że jest to późna odpowiedź, pomyślałem, że wrócę na wypadek, gdyby była przydatna dla kogoś innego:

def find_dict_in_list(dicts, default=None, **kwargs):
    """Find first matching :obj:`dict` in :obj:`list`.

    :param list dicts: List of dictionaries.
    :param dict default: Optional. Default dictionary to return.
        Defaults to `None`.
    :param **kwargs: `key=value` pairs to match in :obj:`dict`.

    :returns: First matching :obj:`dict` from `dicts`.
    :rtype: dict

    """

    rval = default
    for d in dicts:
        is_found = False

        # Search for keys in dict.
        for k, v in kwargs.items():
            if d.get(k, None) == v:
                is_found = True

            else:
                is_found = False
                break

        if is_found:
            rval = d
            break

    return rval


if __name__ == '__main__':
    # Tests
    dicts = []
    keys = 'spam eggs shrubbery knight'.split()

    start = 0
    for _ in range(4):
        dct = {k: v for k, v in zip(keys, range(start, start+4))}
        dicts.append(dct)
        start += 4

    # Find each dict based on 'spam' key only.  
    for x in range(len(dicts)):
        spam = x*4
        assert find_dict_in_list(dicts, spam=spam) == dicts[x]

    # Find each dict based on 'spam' and 'shrubbery' keys.
    for x in range(len(dicts)):
        spam = x*4
        assert find_dict_in_list(dicts, spam=spam, shrubbery=spam+2) == dicts[x]

    # Search for one correct key, one incorrect key:
    for x in range(len(dicts)):
        spam = x*4
        assert find_dict_in_list(dicts, spam=spam, shrubbery=spam+1) is None

    # Search for non-existent dict.
    for x in range(len(dicts)):
        spam = x+100
        assert find_dict_in_list(dicts, spam=spam) is None
Doug R.
źródło
0

Większość (jeśli nie wszystkie) implementacje tutaj zaproponowane mają dwie wady:

  • Zakładają, że do przeszukania przekazywany jest tylko jeden klucz, a może być interesujący mieć więcej do skomplikowanego dyktowania
  • Zakładają, że wszystkie klucze przekazane do wyszukiwania istnieją w nagraniach, dlatego nie radzą sobie poprawnie z KeyError występującym, gdy tak nie jest.

Zaktualizowana propozycja:

def find_first_in_list(objects, **kwargs):
    return next((obj for obj in objects if
                 len(set(obj.keys()).intersection(kwargs.keys())) > 0 and
                 all([obj[k] == v for k, v in kwargs.items() if k in obj.keys()])),
                None)

Może nie najbardziej pythoniczny, ale przynajmniej nieco bardziej bezpieczny w razie awarii.

Stosowanie:

>>> obj1 = find_first_in_list(list_of_dict, name='Pam', age=7)
>>> obj2 = find_first_in_list(list_of_dict, name='Pam', age=27)
>>> obj3 = find_first_in_list(list_of_dict, name='Pam', address='nowhere')
>>> 
>>> print(obj1, obj2, obj3)
{"name": "Pam", "age": 7}, None, {"name": "Pam", "age": 7}

Sens .

onekiloparsec
źródło