Temukan indeks dikt dalam daftar, dengan mencocokkan nilai dikt tersebut

131

Saya punya daftar dicts:

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

Bagaimana cara efisien menemukan posisi indeks [0], [1], atau [2] dengan mencocokkan nama = 'Tom'?

Jika ini adalah daftar satu dimensi saya bisa melakukan list.index () tapi saya tidak yakin bagaimana untuk melanjutkan dengan mencari nilai dicts dalam daftar.

menjerat
sumber
6
"daftar" adalah konstruktor daftar, Anda sebaiknya memilih nama lain untuk daftar (bahkan dalam contoh). Dan apa yang seharusnya menjadi respons jika tidak ada elemen yang ditemukan? ajukan pengecualian? kembali Tidak ada?
tokland
7
Jika Anda akan sangat membutuhkan ini, gunakan struktur data yang lebih tepat (mungkin { 'Jason': {'id': '1234'}, 'Tom': {'id': '1245'}, ...}?)
3
@delnan Karena itu resep untuk bencana! Jika ada, itu seharusnya {'1234': {'name': 'Jason'}, ...}. Bukan berarti itu akan membantu kasus penggunaan ini.
OJFord

Jawaban:

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

Jika Anda perlu mengambil berulang kali dari nama, Anda harus mengindeksnya dengan nama (menggunakan kamus), cara mendapatkan operasi ini akan menjadi O (1) waktu. Sebuah ide:

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
sumber
2
IMHO ini tidak dapat dibaca atau Pythonic adalah jawaban @ Emile. Karena niatnya tidak benar-benar untuk membuat generator (dan menggunakan next()untuk ini terasa aneh bagi saya), tujuannya hanya untuk mendapatkan indeks. Juga, ini meningkatkan StopIteration, sedangkan lst.index()metode Python meningkatkan ValueError.
Ben Hoyt
@ benhoyt: Saya juga tidak suka pengecualian StopIteration, tetapi sementara Anda dapat mengubah nilai default next (), pengecualian yang dimunculkan tetap. Pythonicity agak subyektif jadi saya tidak akan membantahnya, mungkin for-loop lebih pythonic. Di sisi lain, beberapa orang alias next () untuk first (), dan itu pasti terdengar lebih baik: pertama (indeks untuk (indeks, d) di ...).
tokland
first()memang terdengar lebih baik. Anda selalu dapat mencoba / kecuali StopIteration dan meningkatkan ValueError sehingga pemanggil memiliki konsistensi. Atau tetapkan next()default ke -1.
Ben Hoyt
1
@ gdw2: Saya mengerti SyntaxError: Generator expression must be parenthesized if not sole argumentketika melakukan itu.
avoliva
2
@avoliva menambahkan tanda kurung di sekitar berikutnya seperti berikutnext((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)
HussienK
45

Versi yang mudah dibaca adalah

def find(lst, key, value):
    for i, dic in enumerate(lst):
        if dic[key] == value:
            return i
    return -1
Emile
sumber
8
Ini sepertinya yang paling mudah dibaca dan Pythonic. Ini juga meniru perilaku str.find()baik. Anda juga bisa memanggilnya index()dan menaikkan ValueErrorbukannya mengembalikan -1 jika itu lebih baik.
Ben Hoyt
6
Setuju - dengan mengembalikan -1 saat tidak ada yang cocok, Anda akan selalu mendapatkan dict terakhir dalam daftar, yang mungkin bukan yang Anda inginkan. Lebih baik mengembalikan Tidak ada dan memeriksa keberadaan kecocokan dalam kode panggilan.
shacker
9

Ini tidak akan efisien, karena Anda harus memeriksa daftar setiap item di dalamnya (O (n)). Jika Anda ingin efisiensi, Anda dapat menggunakan dict dari dicts . Pada pertanyaan, inilah salah satu cara yang mungkin untuk menemukannya (walaupun, jika Anda ingin tetap berpegang pada struktur data ini, sebenarnya lebih efisien menggunakan generator seperti yang ditulis oleh Brent Newey di komentar; lihat juga jawaban 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
lebih buruk
sumber
1
Anda dapat memperoleh efisiensi yang Anda inginkan dengan menggunakan generator. Lihat jawaban tokland.
Brent Newey
2
@Brent Newey: Generator tidak mengubah fakta, bahwa Anda harus melintasi seluruh daftar, menjadikan pencarian O (n) sebagai klaim yang lebih baik ... Tergantung pada berapa lama daftar itu, perbedaan antara menggunakan generator vs menggunakan a for loop atau apa pun yang bisa diabaikan, di mana perbedaan antara menggunakan dict vs menggunakan daftar mungkin tidak
Dirk
@Brent: Anda benar, tetapi bisakah ia mengalahkan pencarian O (1) dalam kamus, apalagi jika item yang dicari ada di akhir daftar?
setelah
1
@ Perang Panggilan berikutnya () pada generator berhenti ketika kecocokan ditemukan, oleh karena itu tidak harus melintasi seluruh daftar.
Brent Newey
@ meter Anda membuat poin yang adil. Saya mengacu pada kemampuan untuk berhenti ketika kecocokan ditemukan.
Brent Newey
2

Berikut adalah fungsi yang menemukan posisi indeks kamus jika ada.

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

Tampaknya paling logis untuk menggunakan kombo filter / indeks:

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

Dan jika Anda berpikir mungkin ada beberapa pertandingan:

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

Jawaban yang ditawarkan oleh @faham bagus sekali, tetapi tidak mengembalikan indeks ke kamus yang berisi nilai. Sebaliknya ia mengembalikan kamus itu sendiri. Berikut ini cara sederhana untuk mendapatkannya: Daftar indeks satu atau lebih jika ada lebih dari satu, atau daftar kosong jika tidak ada:

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

Keluaran:

>>> [1]

Yang saya sukai dari pendekatan ini adalah bahwa dengan edit sederhana Anda bisa mendapatkan daftar indeks dan kamus sebagai tupel. Ini adalah masalah yang saya butuhkan untuk menyelesaikan dan menemukan jawaban ini. Berikut ini, saya menambahkan nilai duplikat di kamus yang berbeda untuk menunjukkan cara kerjanya:

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

Keluaran:

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

Solusi ini menemukan semua kamus yang mengandung 'Tom' dalam nilai mereka.

dengan tenang
sumber
1

Satu liner !?

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

Untuk tertentu, more_itertools.locatemenghasilkan posisi item yang memenuhi predikat.

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_itertoolsadalah perpustakaan pihak ketiga yang mengimplementasikan resep itertools di antara alat-alat lain yang bermanfaat.

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