รายการ Python ของการค้นหาพจนานุกรม


450

สมมติว่าฉันมีสิ่งนี้:

[
{"name": "Tom", "age": 10},
{"name": "Mark", "age": 5},
{"name": "Pam", "age": 7}
]

และโดยการค้นหา "Pam" เป็นชื่อฉันต้องการดึงพจนานุกรมที่เกี่ยวข้อง: {name: "Pam", age: 7}

ทำอย่างไรจึงจะได้สิ่งนี้?

คำตอบ:


514

คุณสามารถใช้นิพจน์ตัวสร้าง :

>>> dicts = [
...     { "name": "Tom", "age": 10 },
...     { "name": "Mark", "age": 5 },
...     { "name": "Pam", "age": 7 },
...     { "name": "Dick", "age": 12 }
... ]

>>> next(item for item in dicts if item["name"] == "Pam")
{'age': 7, 'name': 'Pam'}

หากคุณต้องการจัดการรายการที่ไม่ได้อยู่ที่นั่นคุณสามารถทำสิ่งที่ผู้ใช้Matt แนะนำในความคิดเห็นของเขาและให้ค่าเริ่มต้นโดยใช้ API ที่แตกต่างกันเล็กน้อย:

next((item for item in dicts if item["name"] == "Pam"), None)

และเพื่อค้นหาดัชนีของไอเท็มแทนที่จะเป็นไอเท็มคุณสามารถระบุ ()รายการ:

next((i for i, item in enumerate(dicts) if item["name"] == "Pam"), None)

230
เพียงเพื่อช่วยคนอื่นในเวลาเพียงเล็กน้อยถ้าคุณต้องการค่าเริ่มต้นในเหตุการณ์ "Pam" เพียงไม่อยู่ในรายการ: ถัดไป ((รายการสำหรับรายการเป็น dicts ถ้ารายการ ["ชื่อ"] == "Pam") , ไม่มี)
แมตต์

1
เกี่ยวกับ[item for item in dicts if item["name"] == "Pam"][0]อะไร
Moberg

3
@Moberg นั้นยังคงเป็นรายการที่เข้าใจดังนั้นมันจะวนซ้ำในลำดับการป้อนข้อมูลทั้งหมดโดยไม่คำนึงถึงตำแหน่งของรายการที่ตรงกัน
Frédéric Hamidi

7
สิ่งนี้จะทำให้เกิดข้อผิดพลาด stopiteration หากไม่มีคีย์ในพจนานุกรม
Kishan

3
@Siemkowski: แล้วเพิ่มการสร้างดัชนีที่ทำงาน:enumerate() next(i for i, item in enumerate(dicts) if item["name"] == "Pam")
Martijn Pieters

218

นี่เป็นวิธีที่ฉันคิดว่าเป็นไปได้มากที่สุด:

people = [
{'name': "Tom", 'age': 10},
{'name': "Mark", 'age': 5},
{'name': "Pam", 'age': 7}
]

filter(lambda person: person['name'] == 'Pam', people)

ผลลัพธ์ (ส่งคืนเป็นรายการใน Python 2):

[{'age': 7, 'name': 'Pam'}]

หมายเหตุ: ใน Python 3 วัตถุตัวกรองจะถูกส่งกลับ ดังนั้นโซลูชัน python3 จะเป็น:

list(filter(lambda person: person['name'] == 'Pam', people))

14
น่าสังเกตว่าคำตอบนี้ส่งคืนรายการที่ตรงกับ 'Pam' ในคนหรือมิฉะนั้นเราสามารถรับรายชื่อคนทั้งหมดที่ไม่ใช่ 'Pam' ได้โดยเปลี่ยนโอเปอเรเตอร์การเปรียบเทียบเป็น! = +1
Onema

2
นอกจากนี้มูลค่าการกล่าวถึงว่าผลลัพธ์เป็นวัตถุตัวกรองไม่ใช่รายการ - หากคุณต้องการใช้สิ่งต่าง ๆ เช่นlen()คุณต้องโทรหาlist()ผลลัพธ์ก่อน หรือ: stackoverflow.com/questions/19182188/…
wasabigeek

@wasabigeek นี่คือสิ่งที่ Python 2.7 ของฉันพูดว่า: people = [{'ชื่อ': "Tom", 'อายุ': 10}, {'name': "Mark", 'อายุ': 5}, {'name': "แพม" 'อายุ': 7}] r = กรอง (คนแลมบ์ดา: คน [ 'ชื่อ'] == 'แพม' คน) ประเภท (R) รายการดังนั้นrเป็นlist
PaoloC

1
ความเข้าใจในรายการถือว่าเป็น Pythonic มากกว่าแผนที่ / ตัวกรอง / ลด: stackoverflow.com/questions/5426754/google-python-style-guide
jrc

2
รับนัดแรก:next(filter(lambda x: x['name'] == 'Pam', dicts))
xgMz

60

คำตอบของ @ Frédéric Hamidi นั้นยอดเยี่ยม ใน Python 3.x ไวยากรณ์สำหรับการ.next()เปลี่ยนแปลงเล็กน้อย ดังนั้นการปรับเปลี่ยนเล็กน้อย:

>>> dicts = [
     { "name": "Tom", "age": 10 },
     { "name": "Mark", "age": 5 },
     { "name": "Pam", "age": 7 },
     { "name": "Dick", "age": 12 }
 ]
>>> next(item for item in dicts if item["name"] == "Pam")
{'age': 7, 'name': 'Pam'}

ตามที่กล่าวไว้ในความคิดเห็นโดย @Matt คุณสามารถเพิ่มค่าเริ่มต้นเช่น:

>>> next((item for item in dicts if item["name"] == "Pam"), False)
{'name': 'Pam', 'age': 7}
>>> next((item for item in dicts if item["name"] == "Sam"), False)
False
>>>

1
นี่คือคำตอบที่ดีที่สุดสำหรับ Python 3.x หากคุณต้องการองค์ประกอบเฉพาะจาก dicts เช่นอายุคุณสามารถเขียน: ถัดไป ((item.get ('อายุ') สำหรับรายการเป็น dicts หากรายการ ["ชื่อ"] == "Pam"), เท็จ)
cwhisperer

47

คุณสามารถใช้list comprehension :

def search(name, people):
    return [element for element in people if element['name'] == name]

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

โปรดทราบว่านี่จะส่งคืนรายการ!
Abbas

34
people = [
{'name': "Tom", 'age': 10},
{'name': "Mark", 'age': 5},
{'name': "Pam", 'age': 7}
]

def search(name):
    for p in people:
        if p['name'] == name:
            return p

search("Pam")

มันจะกลับพจนานุกรมแรกในรายการที่มีชื่อที่กำหนด
Ricky Robinson

5
เพียงเพื่อทำให้กิจวัตรที่มีประโยชน์มากนี้เป็นเรื่องธรรมดามากกว่า:def search(list, key, value): for item in list: if item[key] == value: return item
แจ็คเจมส์

30

ฉันทดสอบวิธีการต่าง ๆ เพื่อดูรายการพจนานุกรมและส่งคืนพจนานุกรมโดยที่คีย์ x มีค่าบางอย่าง

ผล:

  • ความเร็ว: ความเข้าใจในรายการ> นิพจน์ตัวสร้าง >> การวนซ้ำรายการปกติ >>> ตัวกรอง
  • ขนาดเชิงเส้นทั้งหมดพร้อมจำนวน dicts ในรายการ (ขนาดรายการ 10x -> เวลา 10x)
  • ปุ่มต่อพจนานุกรมไม่ส่งผลกระทบต่อความเร็วอย่างมีนัยสำคัญสำหรับจำนวนมาก (หลายพัน) ของคีย์ โปรดดูกราฟนี้ที่ฉันคำนวณ: https://imgur.com/a/quQzv (ชื่อวิธีดูด้านล่าง)

การทดสอบทั้งหมดทำด้วยPython 3.6 .4, W7x64

from random import randint
from timeit import timeit


list_dicts = []
for _ in range(1000):     # number of dicts in the list
    dict_tmp = {}
    for i in range(10):   # number of keys for each dict
        dict_tmp[f"key{i}"] = randint(0,50)
    list_dicts.append( dict_tmp )



def a():
    # normal iteration over all elements
    for dict_ in list_dicts:
        if dict_["key3"] == 20:
            pass

def b():
    # use 'generator'
    for dict_ in (x for x in list_dicts if x["key3"] == 20):
        pass

def c():
    # use 'list'
    for dict_ in [x for x in list_dicts if x["key3"] == 20]:
        pass

def d():
    # use 'filter'
    for dict_ in filter(lambda x: x['key3'] == 20, list_dicts):
        pass

ผล:

1.7303 # normal list iteration 
1.3849 # generator expression 
1.3158 # list comprehension 
7.7848 # filter

ฉันได้เพิ่มฟังก์ชั่น z () ที่ใช้ถัดจากชี้โดยFrédéric Hamidi ด้านบน นี่คือผลลัพธ์จากโปรไฟล์ Py
ลีออน

10

หากต้องการเพิ่มเพียงเล็กน้อยใน @ FrédéricHamidi

ในกรณีที่คุณไม่แน่ใจว่ามีคีย์อยู่ในรายการ dicts สิ่งนี้จะช่วยได้:

next((item for item in dicts if item.get("name") and item["name"] == "Pam"), None)

หรือเพียงitem.get("name") == "Pam"
Andreas Haferburg

10

คุณเคยลองใช้แพคเกจแพนด้าหรือไม่? เหมาะสำหรับงานค้นหาประเภทนี้และได้รับการปรับปรุงให้ดีที่สุด

import pandas as pd

listOfDicts = [
{"name": "Tom", "age": 10},
{"name": "Mark", "age": 5},
{"name": "Pam", "age": 7}
]

# Create a data frame, keys are used as column headers.
# Dict items with the same key are entered into the same respective column.
df = pd.DataFrame(listOfDicts)

# The pandas dataframe allows you to pick out specific values like so:

df2 = df[ (df['name'] == 'Pam') & (df['age'] == 7) ]

# Alternate syntax, same thing

df2 = df[ (df.name == 'Pam') & (df.age == 7) ]

ฉันได้เพิ่มการเปรียบเทียบเล็กน้อยด้านล่างเพื่อแสดงให้เห็นถึง runtimes ที่เร็วขึ้นของแพนด้าในขนาดที่ใหญ่ขึ้นเช่น 100k + รายการ:

setup_large = 'dicts = [];\
[dicts.extend(({ "name": "Tom", "age": 10 },{ "name": "Mark", "age": 5 },\
{ "name": "Pam", "age": 7 },{ "name": "Dick", "age": 12 })) for _ in range(25000)];\
from operator import itemgetter;import pandas as pd;\
df = pd.DataFrame(dicts);'

setup_small = 'dicts = [];\
dicts.extend(({ "name": "Tom", "age": 10 },{ "name": "Mark", "age": 5 },\
{ "name": "Pam", "age": 7 },{ "name": "Dick", "age": 12 }));\
from operator import itemgetter;import pandas as pd;\
df = pd.DataFrame(dicts);'

method1 = '[item for item in dicts if item["name"] == "Pam"]'
method2 = 'df[df["name"] == "Pam"]'

import timeit
t = timeit.Timer(method1, setup_small)
print('Small Method LC: ' + str(t.timeit(100)))
t = timeit.Timer(method2, setup_small)
print('Small Method Pandas: ' + str(t.timeit(100)))

t = timeit.Timer(method1, setup_large)
print('Large Method LC: ' + str(t.timeit(100)))
t = timeit.Timer(method2, setup_large)
print('Large Method Pandas: ' + str(t.timeit(100)))

#Small Method LC: 0.000191926956177
#Small Method Pandas: 0.044392824173
#Large Method LC: 1.98827004433
#Large Method Pandas: 0.324505090714

7

นี่เป็นวิธีทั่วไปในการค้นหาค่าในรายการพจนานุกรม:

def search_dictionaries(key, value, list_of_dictionaries):
    return [element for element in list_of_dictionaries if element[key] == value]

6
names = [{'name':'Tom', 'age': 10}, {'name': 'Mark', 'age': 5}, {'name': 'Pam', 'age': 7}]
resultlist = [d    for d in names     if d.get('name', '') == 'Pam']
first_result = resultlist[0]

นี่เป็นวิธีหนึ่ง ...


1
ฉันอาจแนะนำ [d สำหรับ x ในชื่อถ้า d.get ('name', '') == 'Pam'] ... เพื่อจัดการรายการใด ๆ ใน "names" ซึ่งไม่มีคีย์ "name"
Jim Dennis

6

เพียงใช้รายการความเข้าใจ:

[i for i in dct if i['name'] == 'Pam'][0]

รหัสตัวอย่าง:

dct = [
    {'name': 'Tom', 'age': 10},
    {'name': 'Mark', 'age': 5},
    {'name': 'Pam', 'age': 7}
]

print([i for i in dct if i['name'] == 'Pam'][0])

> {'age': 7, 'name': 'Pam'}

5

คุณสามารถทำได้ด้วยการใช้ตัวกรองและวิธีการถัดไปใน Python

filter วิธีการกรองลำดับที่กำหนดและส่งกลับ iterator nextวิธีการยอมรับ iterator และส่งกลับองค์ประกอบต่อไปในรายการ

ดังนั้นคุณสามารถหาองค์ประกอบโดย

my_dict = [
    {"name": "Tom", "age": 10},
    {"name": "Mark", "age": 5},
    {"name": "Pam", "age": 7}
]

next(filter(lambda obj: obj.get('name') == 'Pam', my_dict), None)

และผลลัพธ์คือ

{'name': 'Pam', 'age': 7}

หมายเหตุ: โค้ดด้านบนจะกลับมาในNoneกรณีที่ไม่พบชื่อที่เรากำลังค้นหา


สิ่งนี้ช้ากว่ารายการความเข้าใจมาก
AnupamChugh

4

ความคิดแรกของฉันคือคุณอาจต้องการพิจารณาสร้างพจนานุกรมของพจนานุกรมเหล่านี้ ... ตัวอย่างเช่นถ้าคุณกำลังจะค้นหามากกว่าสองสามครั้ง

อย่างไรก็ตามนั่นอาจเป็นการปรับให้เหมาะสมก่อนเวลาอันควร จะมีอะไรผิดปกติกับ:

def get_records(key, store=dict()):
    '''Return a list of all records containing name==key from our store
    '''
    assert key is not None
    return [d for d in store if d['name']==key]

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

1
การยืนยันอาจถูกข้ามไปหากโหมดดีบักปิดอยู่
bluppfisk

4
dicts=[
{"name": "Tom", "age": 10},
{"name": "Mark", "age": 5},
{"name": "Pam", "age": 7}
]

from collections import defaultdict
dicts_by_name=defaultdict(list)
for d in dicts:
    dicts_by_name[d['name']]=d

print dicts_by_name['Tom']

#output
#>>>
#{'age': 10, 'name': 'Tom'}

3

วิธีง่ายๆในการใช้ list comprehensions คือถ้า lเป็น list

l = [
{"name": "Tom", "age": 10},
{"name": "Mark", "age": 5},
{"name": "Pam", "age": 7}
]

แล้วก็

[d['age'] for d in l if d['name']=='Tom']

2

คุณสามารถลองสิ่งนี้:

''' lst: list of dictionaries '''
lst = [{"name": "Tom", "age": 10}, {"name": "Mark", "age": 5}, {"name": "Pam", "age": 7}]

search = raw_input("What name: ") #Input name that needs to be searched (say 'Pam')

print [ lst[i] for i in range(len(lst)) if(lst[i]["name"]==search) ][0] #Output
>>> {'age': 7, 'name': 'Pam'} 

1

นี่คือการเปรียบเทียบโดยใช้การวนซ้ำรายการโดยใช้ตัวกรอง + แลมบ์ดาหรือการปรับโครงสร้างใหม่ (หากจำเป็นหรือถูกต้องกับกรณีของคุณ) โค้ดของคุณเป็น dict ของ dicts มากกว่ารายการ dicts

import time

# Build list of dicts
list_of_dicts = list()
for i in range(100000):
    list_of_dicts.append({'id': i, 'name': 'Tom'})

# Build dict of dicts
dict_of_dicts = dict()
for i in range(100000):
    dict_of_dicts[i] = {'name': 'Tom'}


# Find the one with ID of 99

# 1. iterate through the list
lod_ts = time.time()
for elem in list_of_dicts:
    if elem['id'] == 99999:
        break
lod_tf = time.time()
lod_td = lod_tf - lod_ts

# 2. Use filter
f_ts = time.time()
x = filter(lambda k: k['id'] == 99999, list_of_dicts)
f_tf = time.time()
f_td = f_tf- f_ts

# 3. find it in dict of dicts
dod_ts = time.time()
x = dict_of_dicts[99999]
dod_tf = time.time()
dod_td = dod_tf - dod_ts


print 'List of Dictionries took: %s' % lod_td
print 'Using filter took: %s' % f_td
print 'Dict of Dicts took: %s' % dod_td

และผลลัพธ์คือ:

List of Dictionries took: 0.0099310874939
Using filter took: 0.0121960639954
Dict of Dicts took: 4.05311584473e-06

สรุป: การมีพจนานุกรมของ dicts อย่างชัดเจนเป็นวิธีที่มีประสิทธิภาพมากที่สุดในการค้นหาในกรณีเหล่านี้ที่คุณรู้ว่าคุณจะค้นหาด้วยรหัสเท่านั้น วิธีใช้ตัวกรองที่น่าสนใจคือทางออกที่ช้าที่สุด


1

การใช้งานส่วนใหญ่ (หากไม่ใช่ทั้งหมด) ที่เสนอมาที่นี่มีข้อบกพร่องสองประการ:

  • พวกเขาสมมติว่ามีเพียงคีย์เดียวที่จะถูกส่งผ่านเพื่อค้นหาในขณะที่มันอาจน่าสนใจที่จะมีสำหรับ dict ที่ซับซ้อนมากขึ้น
  • พวกเขาถือว่าปุ่มทั้งหมดที่ผ่านการค้นหามีอยู่ใน dicts ดังนั้นจึงไม่สามารถจัดการได้อย่างถูกต้องกับ KeyError ที่เกิดขึ้นเมื่อไม่มี

ข้อเสนอที่อัปเดต:

def find_first_in_list(objects, **kwargs):
    return next((obj for obj in objects if
                 len(set(obj.keys()).intersection(kwargs.keys())) > 0 and
                 all([obj[k] == v for k, v in kwargs.items() if k in obj.keys()])),
                None)

อาจไม่ใช่ pythonic ที่มากที่สุด แต่อย่างน้อยก็อีกสักเล็กน้อยจะไม่ปลอดภัย

การใช้งาน:

>>> obj1 = find_first_in_list(list_of_dict, name='Pam', age=7)
>>> obj2 = find_first_in_list(list_of_dict, name='Pam', age=27)
>>> obj3 = find_first_in_list(list_of_dict, name='Pam', address='nowhere')
>>> 
>>> print(obj1, obj2, obj3)
{"name": "Pam", "age": 7}, None, {"name": "Pam", "age": 7}

สรุปสาระสำคัญสรุปสาระสำคัญ


0

คุณต้องผ่านองค์ประกอบทั้งหมดของรายการ ไม่มีทางลัด!

นอกจากที่อื่นคุณเก็บพจนานุกรมของชื่อที่ชี้ไปยังรายการของรายการ แต่คุณจะต้องดูแลผลที่ตามมาจากการดึงองค์ประกอบจากรายการของคุณ


ในกรณีของรายการที่ไม่ได้เรียงลำดับและคีย์ที่หายไปคำสั่งนี้ถูกต้อง แต่ไม่ใช่โดยทั่วไป หากรายการถูกเรียงลำดับองค์ประกอบทั้งหมดไม่จำเป็นต้องวนซ้ำ นอกจากนี้หากมีการบันทึกระเบียนเดียวและคุณรู้ว่าคีย์นั้นไม่ซ้ำกันหรือต้องการเพียงองค์ประกอบเดียวการทำซ้ำอาจหยุดชะงักด้วยรายการเดียวที่ส่งคืน
user25064

ดูคำตอบของ @ user334856
Melih Yıldız

@ MelihYıldız 'บางทีฉันไม่ชัดเจนในคำสั่งของฉัน โดยใช้รายการความเข้าใจของผู้ใช้ 334856 ในคำตอบstackoverflow.com/a/8653572/512225จะผ่านรายการทั้งหมด สิ่งนี้เป็นการยืนยันคำสั่งของฉัน คำตอบที่คุณอ้างถึงเป็นอีกวิธีหนึ่งที่จะพูดในสิ่งที่ฉันเขียน
jimifiki

0

ฉันพบกระทู้นี้เมื่อฉันค้นหาคำตอบของคำถามเดียวกัน ในขณะที่ฉันรู้ว่ามันเป็นคำตอบที่ล่าช้าฉันคิดว่าฉันมีส่วนร่วมในกรณีที่เป็นประโยชน์กับคนอื่น:

def find_dict_in_list(dicts, default=None, **kwargs):
    """Find first matching :obj:`dict` in :obj:`list`.

    :param list dicts: List of dictionaries.
    :param dict default: Optional. Default dictionary to return.
        Defaults to `None`.
    :param **kwargs: `key=value` pairs to match in :obj:`dict`.

    :returns: First matching :obj:`dict` from `dicts`.
    :rtype: dict

    """

    rval = default
    for d in dicts:
        is_found = False

        # Search for keys in dict.
        for k, v in kwargs.items():
            if d.get(k, None) == v:
                is_found = True

            else:
                is_found = False
                break

        if is_found:
            rval = d
            break

    return rval


if __name__ == '__main__':
    # Tests
    dicts = []
    keys = 'spam eggs shrubbery knight'.split()

    start = 0
    for _ in range(4):
        dct = {k: v for k, v in zip(keys, range(start, start+4))}
        dicts.append(dct)
        start += 4

    # Find each dict based on 'spam' key only.  
    for x in range(len(dicts)):
        spam = x*4
        assert find_dict_in_list(dicts, spam=spam) == dicts[x]

    # Find each dict based on 'spam' and 'shrubbery' keys.
    for x in range(len(dicts)):
        spam = x*4
        assert find_dict_in_list(dicts, spam=spam, shrubbery=spam+2) == dicts[x]

    # Search for one correct key, one incorrect key:
    for x in range(len(dicts)):
        spam = x*4
        assert find_dict_in_list(dicts, spam=spam, shrubbery=spam+1) is None

    # Search for non-existent dict.
    for x in range(len(dicts)):
        spam = x+100
        assert find_dict_in_list(dicts, spam=spam) is None
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.