Tìm chỉ mục của một dict trong danh sách, bằng cách khớp với giá trị của dict


130

Tôi có một danh sách các dicts:

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

Làm cách nào tôi có thể tìm thấy vị trí chỉ mục một cách hiệu quả [0], [1] hoặc [2] bằng cách khớp với name = 'Tom'?

Nếu đây là danh sách một chiều, tôi có thể thực hiện list.index () nhưng tôi không chắc chắn cách tiến hành bằng cách tìm kiếm các giá trị của các ký tự trong danh sách.


6
"list" là hàm tạo danh sách, tốt hơn bạn nên chọn tên khác cho danh sách (ngay cả trong một ví dụ). Và những gì sẽ là phản ứng nếu không tìm thấy yếu tố nào? nêu lên một ngoại lệ? Trả lại không?
tokland

7
Nếu bạn sẽ cần điều này rất nhiều, hãy sử dụng cấu trúc dữ liệu phù hợp hơn (có lẽ { 'Jason': {'id': '1234'}, 'Tom': {'id': '1245'}, ...}?)

3
@delnan Vì đó là một công thức cho thảm họa! Nếu bất cứ điều gì, nó nên được {'1234': {'name': 'Jason'}, ...}. Không phải điều đó sẽ giúp trường hợp sử dụng này.
OJFord

Câu trả lời:


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

Nếu bạn cần tìm nạp nhiều lần từ tên, bạn nên lập chỉ mục cho chúng theo tên (sử dụng từ điển), cách này có được các hoạt động sẽ là O (1) thời gian. Một ý tưởng:

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 đây không phải là có thể đọc được hoặc Pythonic là câu trả lời của @ Emile. Bởi vì ý định không thực sự tạo ra một trình tạo (và sử dụng next()cho điều này có vẻ kỳ lạ đối với tôi), mục đích chỉ là để có được chỉ số. Ngoài ra, điều này làm tăng StopIteration, trong khi lst.index()phương thức Python tăng ValueError.
Ben Hoyt

@benhoyt: Tôi cũng không thích ngoại lệ StopIteration, nhưng trong khi bạn có thể thay đổi giá trị mặc định tiếp theo (), ngoại lệ mà nó tăng lên được sửa. Sự trăn trở có phần chủ quan nên tôi sẽ không tranh cãi về điều đó, có lẽ một vòng lặp for là nhiều pythonic hơn. Mặt khác, một số người đặt bí danh next () cho First () và điều đó chắc chắn nghe tốt hơn: đầu tiên (index cho (index, d) in ...).
tokland

first()nghe có vẻ tốt hơn Bạn luôn có thể thử / ngoại trừ StopIteration và tăng ValueError để người gọi có tính nhất quán. Hoặc đặt next()mặc định là -1.
Ben Hoyt

1
@ gdw2: Tôi nhận được SyntaxError: Generator expression must be parenthesized if not sole argumentkhi làm điều đó.
avoliva

2
@avoliva thêm dấu ngoặc đơn tiếp theo như saunext((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)
HussienK

45

Một phiên bản đơn giản dễ đọc là

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

8
Điều này có vẻ dễ đọc nhất và Pythonic. Nó cũng bắt chước hành vi của str.find()độc đáo. Bạn cũng có thể gọi nó index()và tăng ValueErrorthay vì trả về -1 nếu thích hợp hơn.
Ben Hoyt

6
Đồng ý - bằng cách trả về -1 khi không tìm thấy kết quả khớp, bạn sẽ luôn nhận được lệnh cuối cùng trong danh sách, đây có thể không phải là điều bạn muốn. Tốt hơn là trả về Không và kiểm tra sự tồn tại của một trận đấu trong mã cuộc gọi.
Shacker

9

Nó sẽ không hiệu quả, vì bạn cần đi bộ danh sách kiểm tra mọi mục trong đó (O (n)). Nếu bạn muốn hiệu quả, bạn có thể sử dụng dict của dicts . Về câu hỏi, đây là một cách có thể để tìm thấy nó (mặc dù, nếu bạn muốn bám vào cấu trúc dữ liệu này, thực sự hiệu quả hơn khi sử dụng một trình tạo như Brent Newey đã viết trong các bình luận; xem thêm câu trả lời của 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

1
Bạn có thể đạt được hiệu quả mà bạn mong muốn bằng cách sử dụng một máy phát điện. Xem câu trả lời của tokland.
Brent Newey

2
@Brent Newey: Trình tạo không thay đổi thực tế, rằng bạn phải duyệt toàn bộ danh sách, làm cho tìm kiếm O (n) như một tuyên bố của aeter ... Tùy thuộc vào danh sách đó dài bao nhiêu, sự khác biệt giữa việc sử dụng trình tạo so với sử dụng một vòng lặp for hoặc bất cứ điều gì có thể bị bỏ qua, sẽ tạo ra sự khác biệt giữa việc sử dụng một dict so với việc sử dụng một danh sách có thể không
Dirk

@Brent: Bạn đúng, nhưng nó có thể đánh bại tra cứu O (1) trong từ điển không, hơn nữa nếu mục được tìm kiếm nằm ở cuối danh sách?
aeter

1
@Dirk Cuộc gọi () tiếp theo trên trình tạo dừng khi tìm thấy kết quả khớp, do đó nó không phải duyệt toàn bộ danh sách.
Brent Newey

@aeter Bạn làm điểm công bằng. Tôi đã đề cập đến việc có thể dừng lại khi tìm thấy một trận đấu.
Brent Newey

2

Đây là một chức năng tìm vị trí chỉ mục của từ điển nếu nó tồn tại.

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

Có vẻ hợp lý nhất để sử dụng kết hợp bộ lọc / chỉ mục:

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

Và nếu bạn nghĩ rằng có thể có nhiều trận đấu:

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

2

Câu trả lời được cung cấp bởi @faham là một lớp lót đẹp, nhưng nó không trả lại chỉ mục cho từ điển chứa giá trị. Thay vào đó, nó trả về từ điển chính nó. Đây là một cách đơn giản để nhận: Danh sách các chỉ mục một hoặc nhiều nếu có nhiều hơn một hoặc danh sách trống nếu không có:

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

Đầu ra:

>>> [1]

Điều tôi thích về cách tiếp cận này là với một chỉnh sửa đơn giản, bạn có thể nhận được một danh sách cả các chỉ mục và từ điển dưới dạng tuples. Đây là vấn đề tôi cần để giải quyết và tìm thấy những câu trả lời này. Sau đây, tôi đã thêm một giá trị trùng lặp trong một từ điển khác để cho thấy cách thức hoạt động của nó:

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

Đầu ra:

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

Giải pháp này tìm thấy tất cả các từ điển có chứa 'Tom' trong bất kỳ giá trị nào của chúng.


1

Lót!?

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

0

Đối với một lần lặp nhất định, more_itertools.locatemang lại vị trí của các mục thỏa mãn một vị ngữ.

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_itertoolslà một thư viện của bên thứ ba thực hiện các công thức nấu ăn itertools trong số các công cụ hữu ích khác.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.