Bir diktenin değerini eşleştirerek bir listedeki bir diktenin dizinini bulun


131

Bir sözler listem var:

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

[0], [1] veya [2] dizin konumunu name = 'Tom' ile eşleştirerek nasıl verimli bir şekilde bulabilirim?

Bu tek boyutlu bir liste olsaydı list.index () yapabilirdim ama listedeki diktlerin değerlerini arayarak nasıl ilerleyeceğimi bilemiyorum.


6
"liste" liste yapıcısıdır, bir liste için başka bir ad seçseniz iyi olur (bir örnekte bile). Ve hiçbir öğe bulunmazsa yanıt ne olmalıdır? bir istisna yaratmak? dönüş Yok mu?
tokland

7
Buna çok ihtiyacınız olacaksa, daha uygun bir veri yapısı kullanın (belki { 'Jason': {'id': '1234'}, 'Tom': {'id': '1245'}, ...}?)

3
@delnan Çünkü bu bir felaket reçetesi! Bir şey varsa, olmalı {'1234': {'name': 'Jason'}, ...}. Bu kullanım durumuna yardımcı olacağından değil.
OJFord

Yanıtlar:


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

Eğer adından sürekli getirmesi gerekiyorsa, (bir sözlük kullanarak) adıyla endeks onları gerektiği, bu şekilde olsun işlemleri O (1) zaman olabilir. Bir fikir:

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 bu okunabilir değil veya Pythonic @ Emile'nin cevabı. Amaç gerçekten bir jeneratör yaratmak olmadığından (ve bunun için kullanmak next()bana tuhaf geliyor), amaç sadece indeksi elde etmektir. Ayrıca, bu StopIteration'ı yükseltirken Python lst.index()yöntemi ValueError'ı yükseltir.
Ben Hoyt

@benhoyt: StopIteration istisnasını da sevmiyorum, ancak next () 'nin varsayılan değerini değiştirebilirken, ortaya çıkardığı istisna düzeltildi. Pythonisite biraz özneldir, bu yüzden buna itiraz etmeyeceğim, muhtemelen bir for-loop daha pitoniktir. Öte yandan, bazı insanlar ilk () için next () için takma ad verir ve bu kesinlikle kulağa daha iyi gelir: ilk (... içinde (dizin, d) için dizin).
tokland

first()daha iyi geliyor. Her zaman StopIteration'ı deneyebilir / hariç tutabilir ve arayıcının tutarlı olması için ValueError'ı yükseltebilirsiniz. Alternatif olarak next()'s varsayılanını -1 olarak ayarlayın .
Ben Hoyt

1
@ gdw2: Bunu SyntaxError: Generator expression must be parenthesized if not sole argumentyaparken anlıyorum .
avoliva

2
@avoliva, etrafına aşağıdaki gibi bir parantez ekleyinnext((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)
HussienK

45

Okunabilir basit bir versiyon

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

8
Bu en okunaklı ve Pythonic gibi görünüyor. Aynı zamanda davranışını da str.find()güzelce taklit eder . Ayrıca isterseniz çağırabilir index()ve ValueError-1 döndürmek yerine a yükseltebilirsiniz .
Ben Hoyt

6
Kabul edildi - hiçbir eşleşme bulunmadığında -1 döndürürseniz, listedeki her zaman son hükmü alırsınız, ki bu muhtemelen istediğiniz şey değildir. Yok döndürmek ve çağıran kodda bir eşleşme olup olmadığını kontrol etmek daha iyidir.
shacker

9

Listedeki her öğeyi kontrol etmeniz gerektiğinden verimli olmayacaktır (O (n)). Verimlilik istiyorsanız , dikteler kullanabilirsiniz . Soruyla ilgili olarak, onu bulmanın olası bir yolu şudur (yine de, bu veri yapısına bağlı kalmak istiyorsanız , Brent Newey'in yorumlarda yazdığı gibi bir jeneratör kullanmak aslında daha etkilidir ; ayrıca tokland'ın cevabına bakın):

>>> 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
Jeneratör kullanarak arzu ettiğiniz verimi elde edebilirsiniz. Tokland'ın cevabına bakın.
Brent Newey

2
@Brent Newey: Jeneratör, tüm listeyi dolaşmanız gerektiği gerçeğini değiştirmez, aeterin iddia ettiği gibi aramayı O (n) yapar ... Bu listenin ne kadar uzun olduğuna bağlı olarak, bir jeneratör kullanmakla kullanmak arasındaki fark for döngüsü veya önemsiz olabilecek her şey, bir dikt kullanmakla liste kullanmak arasındaki farkın olmayabilir
Dirk

@Brent: Haklısın, ama bir sözlükte O (1) aramasını geçebilir mi, dahası aranan öğe listenin sonunda ise?
aeter

1
@Dirk Jeneratördeki sonraki () çağrı bir eşleşme bulunduğunda durur, bu nedenle tüm listeyi taramak zorunda değildir.
Brent Newey

@aeter Adil bir noktaya değindiniz. Bir eşleşme bulunduğunda durabilmekten bahsediyordum.
Brent Newey

2

Burada sözlüğün varsa dizin konumunu bulan bir işlev var.

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

Bir filtre / dizin birleşimini kullanmak en mantıklı görünüyor:

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

Ve birden fazla eşleşme olabileceğini düşünüyorsanız:

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

2

@Faham tarafından sunulan cevap güzel bir tek satırlık cevaptır, ancak dizini değeri içeren sözlüğe döndürmez. Bunun yerine sözlüğün kendisini döndürür. İşte almanın basit bir yolu: Birden fazla varsa bir veya daha fazla dizin listesi veya yoksa boş bir liste:

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

Çıktı:

>>> [1]

Bu yaklaşımla ilgili sevdiğim şey, basit bir düzenleme ile hem dizinlerin hem de sözlüklerin bir listesini tuple olarak alabilmenizdir. Çözmem ve bu cevapları bulmam gereken sorun buydu. Aşağıda, nasıl çalıştığını göstermek için farklı bir sözlüğe yinelenen bir değer ekledim:

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

Çıktı:

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

Bu çözüm, değerlerinden herhangi birinde 'Tom' içeren tüm sözlükleri bulur.


1

Bir astar !?

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

0

Belirli bir yinelenebilir için, more_itertools.locatebir koşulu karşılayan öğelerin konumlarını verir.

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_itertoolsdiğer kullanışlı araçların yanı sıra itertools tariflerini uygulayan üçüncü taraf bir kitaplıktır .


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.