Python-Liste der Wörterbuchsuche

449

Angenommen, ich habe Folgendes:

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

und indem ich "Pam" als Namen suche, möchte ich das zugehörige Wörterbuch abrufen: {name: "Pam", age: 7}

Wie erreicht man das?

Hellnar
quelle

Antworten:

510

Sie können einen Generatorausdruck verwenden :

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

Wenn Sie das Element behandeln müssen, das nicht vorhanden ist, können Sie das tun, was Benutzer Matt in seinem Kommentar vorgeschlagen hat, und einen Standard mithilfe einer etwas anderen API angeben:

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

Und um den Index des Elements und nicht das Element selbst zu finden, können Sie die Liste auflisten () :

next((i for i, item in enumerate(dicts) if item["name"] == "Pam"), None)
Frédéric Hamidi
quelle
229
Nur um anderen ein wenig Zeit zu sparen, wenn Sie einen Standardwert für den Fall benötigen, dass "Pam" nicht in der Liste enthalten ist: next ((Element für Element in diktiert, wenn Element ["Name"] == "Pam") , Keine)
Matt
1
Was ist mit [item for item in dicts if item["name"] == "Pam"][0]?
Moberg
3
@Moberg, das ist immer noch ein Listenverständnis, daher wird es die gesamte Eingabesequenz durchlaufen, unabhängig von der Position des übereinstimmenden Elements.
Frédéric Hamidi
7
Dies wird Stopiteration Fehler auslösen, wenn Schlüssel nicht im Wörterbuch vorhanden ist
Kishan
3
@Siemkowski: dann hinzufügen enumerate(), um einen laufenden Index zu generieren : next(i for i, item in enumerate(dicts) if item["name"] == "Pam").
Martijn Pieters
217

Das sieht für mich am pythonischsten aus:

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

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

Ergebnis (in Python 2 als Liste zurückgegeben):

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

Hinweis: In Python 3 wird ein Filterobjekt zurückgegeben. Die Python3-Lösung wäre also:

list(filter(lambda person: person['name'] == 'Pam', people))
PaoloC
quelle
14
Es ist erwähnenswert, dass diese Antwort eine Liste mit allen Übereinstimmungen für 'Pam' in Personen zurückgibt. Alternativ können wir eine Liste aller Personen erhalten, die nicht 'Pam' sind, indem wir den Vergleichsoperator auf! = Ändern. +1
Onema
2
Erwähnenswert ist auch, dass das Ergebnis ein Filterobjekt und keine Liste ist. Wenn Sie beispielsweise Dinge verwenden möchten len(), müssen Sie list()zuerst das Ergebnis aufrufen . Oder: stackoverflow.com/questions/19182188/…
wasabigeek
@wasabigeek das sagt mein Python 2.7: people = [{'name': "Tom", 'age': 10}, {'name': "Mark", 'age': 5}, {'name': "Pam", 'Alter': 7}] r = Filter (Lambda Person: Person ['Name'] == 'Pam', Personen) Typ (r) Liste So rist einlist
PaoloC
1
Listenverständnisse gelten als pythonischer als map / filter / redu
jrc
2
Holen Sie sich das erste Spiel:next(filter(lambda x: x['name'] == 'Pam', dicts))
xgMz
60

@ Frédéric Hamidis Antwort ist großartig. In Python 3.x hat sich die Syntax für .next()leicht geändert. Also eine kleine Modifikation:

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

Wie in den Kommentaren von @Matt erwähnt, können Sie einen Standardwert als solchen hinzufügen:

>>> 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.
quelle
1
Dies ist die beste Antwort für Python 3.x. Wenn Sie ein bestimmtes Element aus den Diktaten benötigen, wie z. B. das Alter, können Sie Folgendes schreiben: next ((item.get ('age') für Artikel in Diktaten, wenn item ["name"] == "Pam"), False)
cwhisperer
47

Sie können ein Listenverständnis verwenden :

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

quelle
4
Das ist schön, weil es alle Übereinstimmungen zurückgibt, wenn es mehr als eine gibt. Nicht genau das, wonach die Frage gestellt wurde, aber es ist das, was ich brauchte! Vielen Dank!
user3303554
Beachten Sie auch, dass dies eine Liste zurückgibt!
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
quelle
Es wird das erste Wörterbuch in der Liste mit dem angegebenen Namen zurückgegeben.
Ricky Robinson
5
Nur um diese sehr nützliche Routine etwas allgemeiner zu gestalten:def search(list, key, value): for item in list: if item[key] == value: return item
Jack James
30

Ich habe verschiedene Methoden getestet, um eine Liste von Wörterbüchern durchzugehen und die Wörterbücher zurückzugeben, in denen Schlüssel x einen bestimmten Wert hat.

Ergebnisse:

  • Geschwindigkeit: Listenverständnis> Generatorausdruck >> normale Listeniteration >>> Filter.
  • Alle skalieren linear mit der Anzahl der Dikte in der Liste (10x Listengröße -> 10x Zeit).
  • Die Schlüssel pro Wörterbuch wirken sich bei großen Mengen (Tausenden) von Schlüsseln nicht wesentlich auf die Geschwindigkeit aus. Bitte sehen Sie sich dieses Diagramm an, das ich berechnet habe: https://imgur.com/a/quQzv (Methodennamen siehe unten).

Alle Tests wurden mit Python 3.6 .4, W7x64 durchgeführt.

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

Ergebnisse:

1.7303 # normal list iteration 
1.3849 # generator expression 
1.3158 # list comprehension 
7.7848 # filter
user136036
quelle
Ich habe die Funktion z () hinzugefügt, die als nächstes implementiert wird, wie oben von Frédéric Hamidi gezeigt. Hier sind die Ergebnisse aus dem Py-Profil.
Leon
10

Um @ FrédéricHamidi nur ein kleines bisschen hinzuzufügen.

Falls Sie nicht sicher sind, ob ein Schlüssel in der Liste der Diktate enthalten ist, hilft Folgendes:

next((item for item in dicts if item.get("name") and item["name"] == "Pam"), None)
Drazen Urch
quelle
oder einfachitem.get("name") == "Pam"
Andreas Haferburg
10

Haben Sie jemals das Pandas-Paket ausprobiert? Es ist perfekt für diese Art von Suchaufgabe und auch optimiert.

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

Ich habe unten ein wenig Benchmarking hinzugefügt, um die schnelleren Laufzeiten der Pandas in größerem Maßstab zu veranschaulichen, dh mehr als 100.000 Einträge:

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 Sobh
quelle
7

Dies ist eine allgemeine Methode zum Suchen eines Werts in einer Liste von Wörterbüchern:

def search_dictionaries(key, value, list_of_dictionaries):
    return [element for element in list_of_dictionaries if element[key] == value]
Ipegasus
quelle
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]

Dies ist eine Möglichkeit ...

Niclas Nilsson
quelle
1
Ich könnte [d für x in Namen vorschlagen, wenn d.get ('name', '') == 'Pam'] ... alle Einträge in "Namen", die keinen "Namen" -Schlüssel hatten, ordnungsgemäß behandeln.
Jim Dennis
6

Einfach Listenverständnis verwenden:

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

Beispielcode:

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'}
Teoretic
quelle
5

Sie können dies mit der Verwendung von Filter und nächsten Methoden in Python erreichen.

filterMethode filtert die angegebene Sequenz und gibt einen Iterator zurück. nextDie Methode akzeptiert einen Iterator und gibt das nächste Element in der Liste zurück.

So können Sie das Element finden durch,

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)

und die Ausgabe ist,

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

Hinweis: Der obige Code wird zurückgegeben, Nonefalls der gesuchte Name nicht gefunden wird.

Manoj Kumar S.
quelle
Dies ist viel langsamer als das Listenverständnis.
AnupamChugh
4

Mein erster Gedanke wäre, dass Sie vielleicht ein Wörterbuch dieser Wörterbücher erstellen möchten ... wenn Sie es beispielsweise mehr als selten durchsuchen würden.

Dies könnte jedoch eine vorzeitige Optimierung sein. Was wäre falsch daran:

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
quelle
Tatsächlich können Sie ein Wörterbuch mit dem Namen name = None haben. Aber das würde mit diesem Listenverständnis nicht wirklich funktionieren und es ist wahrscheinlich nicht vernünftig, es in Ihrem Datenspeicher zuzulassen.
Jim Dennis
1
Asserts können übersprungen werden, wenn der Debug-Modus deaktiviert ist.
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
quelle
3

Ein einfacher Weg, Listenverständnisse zu verwenden, ist, ob les sich um die Liste handelt

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

dann

[d['age'] for d in l if d['name']=='Tom']
cvg
quelle
2

Sie können dies versuchen:

''' 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
quelle
1

Hier ist ein Vergleich mit iterierender Throuhg-Liste, Filter + Lambda oder Refactoring (falls erforderlich oder für Ihren Fall gültig) Ihres Codes, um Diktate anstelle von Diktaten zu diktieren

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

Und die Ausgabe ist folgende:

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

Fazit: Ein Wörterbuch mit Diktaten ist eindeutig die effizienteste Methode, um in den Fällen suchen zu können, in denen Sie wissen, dass Sie nur nach IDs suchen. Interessanterweise ist die Verwendung von Filtern die langsamste Lösung.

Kőhalmy Zoltán
quelle
0

Sie müssen alle Elemente der Liste durchgehen. Es gibt keine Abkürzung!

Sofern Sie nicht irgendwo anders ein Wörterbuch mit den Namen führen, die auf die Elemente der Liste verweisen, müssen Sie sich dann um die Konsequenzen kümmern, wenn Sie ein Element aus Ihrer Liste entfernen.

jimifiki
quelle
Bei einer unsortierten Liste und einem fehlenden Schlüssel ist diese Aussage korrekt, aber nicht allgemein. Wenn bekannt ist, dass die Liste sortiert ist, müssen nicht alle Elemente wiederholt werden. Wenn ein einzelner Datensatz getroffen wird und Sie wissen, dass die Schlüssel eindeutig sind oder nur ein Element erfordern, wird die Iteration möglicherweise mit dem zurückgegebenen Einzelelement angehalten.
user25064
siehe die Antwort von @ user334856
Melih Yıldız
@ MelihYıldız 'vielleicht war mir in meiner Aussage nicht klar. Durch Verwendung eines Listenverständnisses durchläuft user334856 in der Antwort stackoverflow.com/a/8653572/512225 die gesamte Liste. Dies bestätigt meine Aussage. Die Antwort, auf die Sie sich beziehen, ist eine andere Art zu sagen, was ich geschrieben habe.
Jimifiki
0

Ich habe diesen Thread gefunden, als ich nach einer Antwort auf dieselbe Frage gesucht habe. Obwohl mir klar ist, dass es eine späte Antwort ist, dachte ich, ich würde sie beitragen, falls es für andere nützlich ist:

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.
quelle
0

Die meisten (wenn nicht alle) hier vorgeschlagenen Implementierungen weisen zwei Mängel auf:

  • Sie gehen davon aus, dass nur ein Schlüssel für die Suche übergeben werden muss, während es für komplexe Diktate interessant sein kann, mehr zu haben
  • Sie gehen davon aus, dass alle für die Suche übergebenen Schlüssel in den Dikten vorhanden sind, und behandeln daher KeyError nicht korrekt, wenn dies nicht der Fall ist.

Ein aktualisierter Vorschlag:

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)

Vielleicht nicht die pythonischste, aber zumindest ein bisschen ausfallsicherer.

Verwendungszweck:

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

Das Wesentliche .

onekiloparsec
quelle