ค้นหาดัชนีของ dict ภายในรายการโดยจับคู่ค่าของ dict


131

ฉันมีรายการ dicts:

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

ฉันจะค้นหาตำแหน่งดัชนี [0], [1] หรือ [2] ได้อย่างมีประสิทธิภาพโดยการจับคู่ในชื่อ = 'Tom' อย่างไร

ถ้านี่เป็นรายการหนึ่งมิติที่ฉันสามารถทำได้ list.index () แต่ฉันไม่แน่ใจว่าจะดำเนินการอย่างไรโดยค้นหาค่าของ dicts ในรายการ


6
"list" เป็นตัวสร้างรายการคุณควรเลือกชื่ออื่นสำหรับรายการ (ดีกว่าในตัวอย่าง) และสิ่งที่ควรตอบสนองหากไม่พบองค์ประกอบ? เพิ่มข้อยกเว้น? ส่งคืนไม่มีหรือไม่
tokland

7
หากคุณต้องการสิ่งนี้มากให้ใช้โครงสร้างข้อมูลที่เหมาะสมกว่า (อาจจะ{ 'Jason': {'id': '1234'}, 'Tom': {'id': '1245'}, ...}?)

3
@delnan เพราะนั่นเป็นสูตรสำหรับภัยพิบัติ! {'1234': {'name': 'Jason'}, ...}ถ้ามีอะไรที่มันควรจะเป็น ไม่ใช่ว่าจะช่วยกรณีใช้นี้
OJFord

คำตอบ:


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

หากคุณต้องการดึงข้อมูลซ้ำ ๆ จากชื่อคุณควรจัดทำดัชนีตามชื่อ (ใช้พจนานุกรม) วิธีนี้จะทำให้การดำเนินการเป็นเวลา O (1) ความคิด:

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 สิ่งนี้ไม่สามารถอ่านได้หรือ Pythonic เป็นคำตอบของ @ Emile เนื่องจากความตั้งใจไม่ใช่เพื่อสร้างเครื่องกำเนิดไฟฟ้า (และการใช้next()สิ่งนี้ดูแปลกสำหรับฉัน) จุดมุ่งหมายเพียงเพื่อรับดัชนี นอกจากนี้ยังเพิ่ม StopIteration ในขณะที่lst.index()วิธีPython ยก ValueError
เบ็นฮอยต์

@benhoyt: ฉันไม่ชอบข้อยกเว้น StopIteration อย่างใดอย่างหนึ่ง แต่ในขณะที่คุณสามารถเปลี่ยนค่าเริ่มต้นของถัดไป () ข้อยกเว้นที่เพิ่มขึ้นได้รับการแก้ไข Pythonicity ค่อนข้างเป็นอัตนัยดังนั้นฉันจะไม่โต้แย้งมันอาจเป็น for-loop มากกว่า pythonic ในทางกลับกันบางคนนามแฝงถัดไป () สำหรับ First () และนั่นฟังดูดีกว่า: First (ดัชนีสำหรับ (index, d) ใน ... )
tokland

first()เสียงจะดีขึ้น คุณสามารถลอง / ยกเว้น StopIteration และเพิ่ม ValueError เพื่อให้ผู้โทรมีความสม่ำเสมอ หรือตั้งค่าnext()เริ่มต้นเป็น -1
Ben Hoyt

1
@ gdw2: ฉันได้รับSyntaxError: Generator expression must be parenthesized if not sole argumentเมื่อทำเช่นนั้น
avoliva

2
@avoliva เพิ่มวงเล็บในตำแหน่งถัดไปดังนี้next((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)
HussienK

45

เวอร์ชันที่อ่านง่ายคือ

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

8
ดูเหมือนว่าจะอ่านได้มากที่สุดและ Pythonic นอกจากนี้ยังเลียนแบบพฤติกรรมของstr.find()อย่างดี คุณสามารถโทรหามันindex()และเพิ่ม a ValueErrorแทนการส่งคืน -1 หากเป็นที่ต้องการ
เบ็นฮอยต์

6
เห็นด้วย - เมื่อกลับ -1 เมื่อไม่พบคู่ที่ตรงกันคุณจะได้รับ dict ล่าสุดในรายการซึ่งอาจไม่ใช่สิ่งที่คุณต้องการ ดีกว่าที่จะกลับไม่มีและตรวจสอบการมีอยู่ของการแข่งขันในรหัสโทร
shacker

9

จะไม่มีประสิทธิภาพเนื่องจากคุณต้องเดินรายการตรวจสอบทุกรายการในนั้น (O (n)) หากคุณต้องการประสิทธิภาพที่คุณสามารถใช้Dict ของ dicts ในคำถามต่อไปนี้เป็นวิธีหนึ่งที่เป็นไปได้ในการค้นหามัน (แม้ว่าถ้าคุณต้องการติดกับโครงสร้างข้อมูลนี้จริง ๆ แล้วมันมีประสิทธิภาพมากกว่าในการใช้เครื่องกำเนิดไฟฟ้าตามที่ Brent Newey เขียนไว้ในความคิดเห็น; ดูคำตอบของ 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
คุณสามารถเพิ่มประสิทธิภาพที่คุณต้องการโดยใช้เครื่องกำเนิดไฟฟ้า ดูคำตอบของ tokland
Brent Newey

2
@Brent Newey: เครื่องกำเนิดไฟฟ้าไม่เปลี่ยนความจริงที่ว่าคุณต้องข้ามรายการทั้งหมดทำให้การค้นหา O (n) เป็นเอเตอร์อ้างว่า ... ขึ้นอยู่กับระยะเวลาที่รายการนั้นความแตกต่างระหว่างการใช้เครื่องกำเนิดไฟฟ้ากับการใช้ สำหรับลูปหรืออะไรก็ตามที่อาจถูกละเลยได้ความแตกต่างระหว่างการใช้ dict กับการใช้ list อาจไม่
Dirk

@Brent: ถูกต้อง แต่มันสามารถเอาชนะการค้นหา O (1) ในพจนานุกรมได้นอกจากนี้หากรายการที่ค้นหาอยู่ท้ายรายการ?
aeter

1
@Dirk โทร () ถัดไปบนตัวกำเนิดหยุดเมื่อพบการแข่งขันดังนั้นจึงไม่จำเป็นต้องข้ามรายการทั้งหมด
Brent Newey

@aeter คุณทำให้เป็นธรรม ฉันหมายถึงความสามารถในการหยุดเมื่อพบการแข่งขัน
Brent Newey

2

นี่คือฟังก์ชันที่ค้นหาตำแหน่งดัชนีของพจนานุกรมหากมีอยู่

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

ดูเหมือนว่าส่วนใหญ่จะใช้คำสั่งผสม filter / index:

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

และถ้าคุณคิดว่าอาจมีการแข่งขันหลายรายการ:

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

2

คำตอบที่เสนอโดย @faham เป็นหนึ่งซับที่ดี แต่มันไม่ได้ส่งคืนดัชนีไปยังพจนานุกรมที่มีค่า แต่จะส่งคืนพจนานุกรมเอง นี่เป็นวิธีง่ายๆในการรับรายการของดัชนีหนึ่งรายการขึ้นไปหากมีมากกว่าหนึ่งรายการหรือรายการว่างเปล่าหากไม่มี:

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

เอาท์พุท:

>>> [1]

สิ่งที่ฉันชอบเกี่ยวกับวิธีนี้คือการแก้ไขอย่างง่ายคุณสามารถรับรายการดัชนีและพจนานุกรมเป็นสิ่งอันดับ นี่เป็นปัญหาที่ฉันต้องแก้ไขและพบคำตอบเหล่านี้ ในต่อไปนี้ฉันเพิ่มค่าที่ซ้ำกันในพจนานุกรมที่แตกต่างกันเพื่อแสดงวิธีการทำงาน:

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

เอาท์พุท:

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

วิธีนี้จะค้นหาพจนานุกรมทั้งหมดที่มี 'Tom' อยู่ในค่าใด ๆ ของพวกเขา



0

สำหรับการ iterable ที่กำหนดmore_itertools.locateจะให้ตำแหน่งของรายการที่ตรงตามเพรดิเคต

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_itertoolsเป็นห้องสมุดบุคคลที่สามที่ใช้สูตร itertoolsในเครื่องมือที่มีประโยชน์อื่น ๆ


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.