Suchen Sie den Index eines Diktats in einer Liste, indem Sie den Wert des Diktats abgleichen

130

Ich habe eine Liste von Diktaten:

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

Wie kann ich die Indexposition [0], [1] oder [2] effizient finden, indem ich auf name = 'Tom' übereinstimme?

Wenn dies eine eindimensionale Liste wäre, könnte ich list.index () ausführen, bin mir aber nicht sicher, wie ich vorgehen soll, indem ich die Werte der Diktate in der Liste suche.

verstricken
quelle
6
"list" ist der Listenkonstruktor. Wählen Sie besser einen anderen Namen für eine Liste (auch in einem Beispiel). Und wie soll die Antwort sein, wenn kein Element gefunden wird? eine Ausnahme auslösen? keine zurückgeben?
tokland
7
Wenn Sie dies häufig benötigen, verwenden Sie eine geeignetere Datenstruktur (vielleicht { 'Jason': {'id': '1234'}, 'Tom': {'id': '1245'}, ...}?)
3
@delnan Weil das ein Rezept für eine Katastrophe ist! Wenn überhaupt, sollte es sein {'1234': {'name': 'Jason'}, ...}. Nicht, dass das diesem Anwendungsfall helfen würde.
OJFord

Antworten:

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

Wenn Sie wiederholt vom Namen abrufen müssen, sollten Sie sie nach Namen indizieren (mithilfe eines Wörterbuchs). Auf diese Weise erhalten Abrufvorgänge die Zeit O (1). Eine Idee:

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
quelle
2
IMHO ist dies nicht so lesbar oder Pythonic ist @ Emiles Antwort. Da die Absicht nicht wirklich darin besteht, einen Generator zu erstellen (und next()dies für mich seltsam erscheint), ist das Ziel nur, den Index zu erhalten. Dies löst auch StopIteration aus, während die Python- lst.index()Methode ValueError auslöst.
Ben Hoyt
@benhoyt: Ich mag die StopIteration-Ausnahme auch nicht, aber während Sie den Standardwert von next () ändern können, ist die Ausnahme, die sie auslöst, behoben. Die Pythonizität ist etwas subjektiv, also werde ich es nicht bestreiten, wahrscheinlich ist eine for-Schleife pythonischer. Auf der anderen Seite aliasen einige Leute next () für first (), und das klingt definitiv besser: first (index für (index, d) in ...).
tokland
first()klingt besser. Sie können jederzeit versuchen / mit Ausnahme der StopIteration und ValueError auslösen, damit der Aufrufer konsistent ist. Alternativ können Sie den next()Standardwert auf -1 setzen.
Ben Hoyt
1
@ gdw2: Ich bekomme SyntaxError: Generator expression must be parenthesized if not sole argumentdabei.
Avoliva
2
@avoliva fügen Sie eine Klammer um wie folgt hinzunext((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)
HussienK
45

Eine einfach lesbare Version ist

def find(lst, key, value):
    for i, dic in enumerate(lst):
        if dic[key] == value:
            return i
    return -1
Emile
quelle
8
Dies scheint am lesbarsten und pythonischsten zu sein. Es ahmt auch das Verhalten von str.find()gut nach. Sie können es auch aufrufen index()und ein erhöhen, ValueErroranstatt -1 zurückzugeben, wenn dies vorzuziehen ist.
Ben Hoyt
6
Einverstanden - Wenn Sie -1 zurückgeben, wenn keine Übereinstimmung gefunden wird, erhalten Sie immer das letzte Diktat in der Liste, was wahrscheinlich nicht das ist, was Sie wollen. Es ist besser, None zurückzugeben und zu prüfen, ob im aufrufenden Code eine Übereinstimmung vorliegt.
Shacker
9

Dies ist nicht effizient, da Sie die Liste durchgehen müssen, um alle darin enthaltenen Elemente zu überprüfen (O (n)). Wenn Sie Effizienz wünschen, können Sie Diktate verwenden . Bei der Frage gibt es eine Möglichkeit, sie zu finden (wenn Sie sich jedoch an diese Datenstruktur halten möchten, ist es tatsächlich effizienter, einen Generator zu verwenden, wie Brent Newey in den Kommentaren geschrieben hat; siehe auch die Antwort von 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
quelle
1
Mit einem Generator können Sie die gewünschte Effizienz erzielen. Siehe toklands Antwort.
Brent Newey
2
@Brent Newey: Der Generator ändert nichts an der Tatsache, dass Sie die gesamte Liste durchlaufen müssen, wodurch die Suche O (n) wird, wie Aeter behauptet ... Je nachdem, wie lang diese Liste ist, besteht der Unterschied zwischen der Verwendung eines Generators und der Verwendung eine for-Schleife oder was auch immer vernachlässigbar sein mag, wobei der Unterschied zwischen der Verwendung eines Diktats und der Verwendung einer Liste möglicherweise nicht
Dirk
@Brent: Sie haben Recht, aber kann es eine O (1) -Suche in einem Wörterbuch schlagen, wenn sich das gesuchte Element am Ende der Liste befindet?
aeter
1
@Dirk Der nächste () Aufruf des Generators stoppt, wenn eine Übereinstimmung gefunden wird, daher muss nicht die gesamte Liste durchlaufen werden.
Brent Newey
@aeter Du machst einen fairen Punkt. Ich bezog mich darauf, aufhören zu können, wenn eine Übereinstimmung gefunden wird.
Brent Newey
2

Hier ist eine Funktion, die die Indexposition des Wörterbuchs ermittelt, falls vorhanden.

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

Scheint am logischsten, eine Filter / Index-Kombination zu verwenden:

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

Und wenn Sie denken, dass es mehrere Übereinstimmungen geben könnte:

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

Die von @faham angebotene Antwort ist ein netter Einzeiler, gibt jedoch den Index nicht an das Wörterbuch zurück, das den Wert enthält. Stattdessen wird das Wörterbuch selbst zurückgegeben. Hier ist ein einfacher Weg, um zu erhalten: Eine Liste von Indizes, eine oder mehrere, wenn es mehr als eine gibt, oder eine leere Liste, wenn es keine gibt:

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

Ausgabe:

>>> [1]

Was mir an diesem Ansatz gefällt, ist, dass Sie mit einer einfachen Bearbeitung eine Liste sowohl der Indizes als auch der Wörterbücher als Tupel erhalten können. Dies ist das Problem, das ich lösen und diese Antworten finden musste. Im Folgenden habe ich einen doppelten Wert in ein anderes Wörterbuch eingefügt, um zu zeigen, wie es funktioniert:

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

Ausgabe:

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

Diese Lösung findet alle Wörterbücher, die 'Tom' in einem ihrer Werte enthalten.

stanely
quelle
1

Einzeiler!?

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

Gibt für eine gegebene Iterable more_itertools.locatePositionen von Elementen aus, die ein Prädikat erfüllen.

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_itertoolsist eine Bibliothek von Drittanbietern, die unter anderem itertools-Rezepte implementiert .

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