วิธีที่ดีที่สุดในการใช้พจนานุกรมที่ซ้อนกันคืออะไร


201

ฉันมีโครงสร้างข้อมูลที่มีความสำคัญต่อพจนานุกรมซ้อนกัน สมมติว่ามันมีลักษณะเช่นนี้:

{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36}}}

ตอนนี้การบำรุงรักษาและการสร้างสิ่งนี้ค่อนข้างเจ็บปวด ทุกครั้งที่ฉันมีรัฐ / เคาน์ตี / อาชีพใหม่ฉันต้องสร้างพจนานุกรมเลเยอร์ที่ต่ำกว่าผ่านบล็อกลอง / จับที่น่ารังเกียจ ยิ่งกว่านั้นฉันต้องสร้างตัววนซ้ำที่น่ารำคาญถ้าฉันต้องการข้ามค่าทั้งหมด

ฉันยังสามารถใช้สิ่งอันดับเป็นกุญแจได้เช่น:

{('new jersey', 'mercer county', 'plumbers'): 3,
 ('new jersey', 'mercer county', 'programmers'): 81,
 ('new jersey', 'middlesex county', 'programmers'): 81,
 ('new jersey', 'middlesex county', 'salesmen'): 62,
 ('new york', 'queens county', 'plumbers'): 9,
 ('new york', 'queens county', 'salesmen'): 36}

สิ่งนี้ทำให้ซ้ำค่ามากง่ายและเป็นธรรมชาติ แต่มันเจ็บปวดกว่า syntactically ที่จะทำสิ่งต่าง ๆ เช่นการรวมตัวและการดูเซ็ตย่อยของพจนานุกรม (เช่นถ้าฉันต้องการไปรัฐโดยรัฐ)

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

ฉันจะทำได้ดีกว่านี้ได้อย่างไร

ภาคผนวก: ฉันรู้setdefault()แต่ก็ไม่ได้ทำเพื่อไวยากรณ์ที่สะอาดจริงๆ นอกจากนี้พจนานุกรมย่อยแต่ละรายการที่คุณสร้างยังต้องsetdefault()ตั้งค่าด้วยตนเอง

คำตอบ:


179

วิธีที่ดีที่สุดในการใช้พจนานุกรมที่ซ้อนกันใน Python คืออะไร

นี่เป็นความคิดที่ไม่ดีอย่าทำ แต่ให้ใช้พจนานุกรมปกติและการใช้งานที่เรื่องดังนั้นเมื่อกุญแจหายไปภายใต้การใช้งานปกติคุณได้รับการคาดหวังdict.setdefault KeyErrorหากคุณยืนยันที่จะรับพฤติกรรมนี้ต่อไปนี้เป็นวิธียิงตัวคุณเองด้วยการเดินเท้า:

ใช้งาน__missing__บนdictคลาสย่อยเพื่อตั้งและส่งคืนอินสแตนซ์ใหม่

วิธีนี้ใช้ได้แล้ว (และบันทึกไว้)ตั้งแต่ Python 2.5 และ (โดยเฉพาะอย่างยิ่งมีค่าสำหรับฉัน) มันพิมพ์สวยเหมือนปกติ dictแทนที่จะพิมพ์น่าเกลียดของ autdivified defaultdict น่าเกลียด:

class Vividict(dict):
    def __missing__(self, key):
        value = self[key] = type(self)() # retain local pointer to value
        return value                     # faster to return than dict lookup

(หมายเหตุself[key]อยู่ที่ด้านซ้ายมือของการมอบหมายดังนั้นจึงไม่มีการเรียกซ้ำที่นี่)

และบอกว่าคุณมีข้อมูล:

data = {('new jersey', 'mercer county', 'plumbers'): 3,
        ('new jersey', 'mercer county', 'programmers'): 81,
        ('new jersey', 'middlesex county', 'programmers'): 81,
        ('new jersey', 'middlesex county', 'salesmen'): 62,
        ('new york', 'queens county', 'plumbers'): 9,
        ('new york', 'queens county', 'salesmen'): 36}

นี่คือรหัสการใช้งานของเรา:

vividict = Vividict()
for (state, county, occupation), number in data.items():
    vividict[state][county][occupation] = number

และตอนนี้:

>>> import pprint
>>> pprint.pprint(vividict, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36}}}

คำวิจารณ์

บทวิจารณ์ของคอนเทนเนอร์ประเภทนี้คือถ้าผู้ใช้สะกดรหัสรหัสของเราอาจล้มเหลวอย่างเงียบ ๆ :

>>> vividict['new york']['queens counyt']
{}

และตอนนี้เรามีเขตที่สะกดผิดในข้อมูลของเรา:

>>> pprint.pprint(vividict, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36},
              'queens counyt': {}}}

คำอธิบาย:

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

หมายเหตุเหล่านี้เป็นความหมายเดียวกันกับคำตอบ upvoted ที่สุด แต่ในครึ่งบรรทัดของรหัส - การใช้งานของ nosklo:

class AutoVivification(dict):
    """Implementation of perl's autovivification feature."""
    def __getitem__(self, item):
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            value = self[item] = type(self)()
            return value

การสาธิตการใช้งาน

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

import pprint

class Vividict(dict):
    def __missing__(self, key):
        value = self[key] = type(self)()
        return value

d = Vividict()

d['foo']['bar']
d['foo']['baz']
d['fizz']['buzz']
d['primary']['secondary']['tertiary']['quaternary']
pprint.pprint(d)

ผลลัพธ์ใด:

{'fizz': {'buzz': {}},
 'foo': {'bar': {}, 'baz': {}},
 'primary': {'secondary': {'tertiary': {'quaternary': {}}}}}

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

ทางเลือกอื่นสำหรับความคมชัด:

dict.setdefault

แม้ว่าผู้ถามจะคิดว่านี่ไม่สะอาด แต่ฉันคิดว่าVividictตัวเองดีกว่า

d = {} # or dict()
for (state, county, occupation), number in data.items():
    d.setdefault(state, {}).setdefault(county, {})[occupation] = number

และตอนนี้:

>>> pprint.pprint(d, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36}}}

การสะกดผิดจะล้มเหลวอย่างเสียงดังและไม่ถ่วงข้อมูลของเราด้วยข้อมูลที่ไม่ดี:

>>> d['new york']['queens counyt']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'queens counyt'

นอกจากนี้ฉันคิดว่า setdefault ใช้งานได้ดีเมื่อใช้ในลูปและคุณไม่รู้ว่าคุณจะได้รับกุญแจอะไร

d = dict()

d.setdefault('foo', {}).setdefault('bar', {})
d.setdefault('foo', {}).setdefault('baz', {})
d.setdefault('fizz', {}).setdefault('buzz', {})
d.setdefault('primary', {}).setdefault('secondary', {}).setdefault('tertiary', {}).setdefault('quaternary', {})

ข้อวิจารณ์อีกข้อหนึ่งคือ setdefault ต้องการอินสแตนซ์ใหม่ไม่ว่าจะใช้หรือไม่ก็ตาม อย่างไรก็ตาม Python (หรืออย่างน้อย CPython) ค่อนข้างฉลาดเกี่ยวกับการจัดการอินสแตนซ์ใหม่ที่ไม่ได้ใช้และไม่อ้างอิงเช่นมันจะนำตำแหน่งในหน่วยความจำกลับมาใช้ใหม่:

>>> id({}), id({}), id({})
(523575344, 523575344, 523575344)

defaultdict ที่กำหนดให้เป็นอัตโนมัติ

นี่เป็นการใช้งานที่ดูเรียบร้อยและการใช้งานในสคริปต์ที่คุณไม่ได้ตรวจสอบข้อมูลจะมีประโยชน์เท่ากับการใช้งาน__missing__:

from collections import defaultdict

def vivdict():
    return defaultdict(vivdict)

แต่ถ้าคุณต้องการตรวจสอบข้อมูลของคุณผลลัพธ์ของ defaultdivified อัตโนมัติที่บรรจุด้วยข้อมูลในลักษณะเดียวกันจะมีลักษณะดังนี้:

>>> d = vivdict(); d['foo']['bar']; d['foo']['baz']; d['fizz']['buzz']; d['primary']['secondary']['tertiary']['quaternary']; import pprint; 
>>> pprint.pprint(d)
defaultdict(<function vivdict at 0x17B01870>, {'foo': defaultdict(<function vivdict 
at 0x17B01870>, {'baz': defaultdict(<function vivdict at 0x17B01870>, {}), 'bar': 
defaultdict(<function vivdict at 0x17B01870>, {})}), 'primary': defaultdict(<function 
vivdict at 0x17B01870>, {'secondary': defaultdict(<function vivdict at 0x17B01870>, 
{'tertiary': defaultdict(<function vivdict at 0x17B01870>, {'quaternary': defaultdict(
<function vivdict at 0x17B01870>, {})})})}), 'fizz': defaultdict(<function vivdict at 
0x17B01870>, {'buzz': defaultdict(<function vivdict at 0x17B01870>, {})})})

ผลลัพธ์นี้ค่อนข้างล่าช้าและผลลัพธ์ไม่สามารถอ่านได้ โซลูชันที่ได้รับโดยทั่วไปคือแปลงกลับเป็น dict ซ้ำเพื่อตรวจสอบด้วยตนเอง วิธีการแก้ปัญหาที่ไม่สำคัญนี้เหลือไว้สำหรับการออกกำลังกายสำหรับผู้อ่าน

ประสิทธิภาพ

สุดท้ายมาดูประสิทธิภาพกัน ฉันลบค่าใช้จ่ายในการเริ่มต้น

>>> import timeit
>>> min(timeit.repeat(lambda: {}.setdefault('foo', {}))) - min(timeit.repeat(lambda: {}))
0.13612580299377441
>>> min(timeit.repeat(lambda: vivdict()['foo'])) - min(timeit.repeat(lambda: vivdict()))
0.2936999797821045
>>> min(timeit.repeat(lambda: Vividict()['foo'])) - min(timeit.repeat(lambda: Vividict()))
0.5354437828063965
>>> min(timeit.repeat(lambda: AutoVivification()['foo'])) - min(timeit.repeat(lambda: AutoVivification()))
2.138362169265747

ขึ้นอยู่กับประสิทธิภาพการdict.setdefaultทำงานที่ดีที่สุด ฉันขอแนะนำอย่างยิ่งให้ใช้รหัสการผลิตในกรณีที่คุณสนใจความเร็วการดำเนินการ

หากคุณต้องการสิ่งนี้สำหรับการใช้งานแบบอินเทอร์แอคทีฟ (ในโน้ตบุ๊ก IPython อาจจะ) ประสิทธิภาพนั้นไม่สำคัญ - ในกรณีนี้ฉันจะใช้ Vividict เพื่ออ่านเอาต์พุต เปรียบเทียบกับวัตถุ AutoVivification (ซึ่งใช้__getitem__แทน__missing__ซึ่งทำขึ้นเพื่อจุดประสงค์นี้) มันเหนือกว่ามาก

ข้อสรุป

การนำไปใช้__missing__กับคลาสย่อยdictเพื่อตั้งค่าและส่งคืนอินสแตนซ์ใหม่นั้นยากกว่าตัวเลือกอื่นเล็กน้อย แต่มีประโยชน์

  • instantiation ง่าย
  • ประชากรข้อมูลง่าย
  • ดูข้อมูลได้ง่าย

และเนื่องจากมีความซับซ้อนน้อยกว่าและมีประสิทธิภาพมากกว่าการปรับเปลี่ยน __getitem__จึงควรเลือกใช้วิธีดังกล่าว

อย่างไรก็ตามมันมีข้อเสีย:

  • การค้นหาที่ไม่ดีจะล้มเหลวอย่างเงียบ ๆ
  • การค้นหาที่ไม่ดีจะยังคงอยู่ในพจนานุกรม

ดังนั้นฉันเองชอบsetdefaultโซลูชันอื่น ๆ และมีในทุกสถานการณ์ที่ฉันต้องการพฤติกรรมแบบนี้


คำตอบที่ยอดเยี่ยม! มีวิธีใดที่จะระบุความลึก จำกัด และประเภทใบไม้สำหรับ a Vividictหรือไม่? เช่น3และlistสำหรับ Dict ของ Dict ของ Dict d['primary']['secondary']['tertiary'].append(element)ของรายการซึ่งอาจจะมีประชากรที่มี ฉันสามารถนิยามคลาสที่แตกต่างกัน 3 คลาสสำหรับแต่ละความลึก แต่ฉันชอบที่จะหาทางแก้ไขที่สะอาดกว่า
Eric Duminil

@EricDuminil d['primary']['secondary'].setdefault('tertiary', []).append('element')- ?? ขอบคุณสำหรับคำชมเชย แต่ให้ฉันจะซื่อสัตย์ - ฉันไม่เคยใช้จริง__missing__- setdefaultฉันมักจะใช้ ฉันอาจจะปรับปรุงข้อสรุปของฉัน / บทนำ ...
แอรอนฮอลล์

@AaronHall พฤติกรรมที่ถูกต้องคือรหัสควรสร้าง dict หากจำเป็น ในกรณีนี้โดยการแทนที่ค่าที่กำหนดไว้ก่อนหน้า
nehem

@AaronHall คุณสามารถช่วยฉันให้เข้าใจสิ่งที่มีความหมายThe bad lookup will remain in the dictionary.ในขณะที่ฉันกำลังพิจารณาใช้โซลูชันนี้อยู่หรือไม่ ชื่นชมมาก ขอบคุณ
nehem

@AaronHall ปัญหาที่เกิดขึ้นกับมันจะล้มเหลวsetdefaultเมื่อซ้อนระดับความลึกมากกว่าสองระดับ ดูเหมือนว่าไม่มีโครงสร้างใน Python ที่สามารถเสนอการเปลี่ยนแปลงอย่างแท้จริงตามที่อธิบายไว้ ผมต้องชำระสำหรับสองวิธีหนึ่งสำหรับการระบุget_nestedและหนึ่งสำหรับset_nestedที่รับการอ้างอิงสำหรับ Dict และรายการของคุณลักษณะที่ซ้อนกัน
nehem

188
class AutoVivification(dict):
    """Implementation of perl's autovivification feature."""
    def __getitem__(self, item):
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            value = self[item] = type(self)()
            return value

การทดสอบ:

a = AutoVivification()

a[1][2][3] = 4
a[1][3][3] = 5
a[1][2]['test'] = 6

print a

เอาท์พุท:

{1: {2: {'test': 6, 3: 4}, 3: {3: 5}}}

ใครมีปัญหานี้เมื่อพวกเขาย้ายไปยังหลาม 3.x? stackoverflow.com/questions/54622935/…
jason

@jason pickleแย่มากระหว่างรุ่นงูหลาม หลีกเลี่ยงการใช้เพื่อจัดเก็บข้อมูลที่คุณต้องการเก็บไว้ ใช้เฉพาะกับแคชและสิ่งต่าง ๆ ที่คุณสามารถถ่ายโอนและสร้างใหม่ได้ตามต้องการ ไม่ได้เป็นวิธีการจัดเก็บข้อมูลระยะยาวหรืออนุกรม
nosklo

คุณใช้อะไรเพื่อเก็บวัตถุเหล่านี้ ออบเจ็กต์การแก้ไขอัตโนมัติของฉันมีแค่ดาต้าดาต้าและสตริงของแพนด้า
jason

@ Jason ขึ้นอยู่กับข้อมูลฉันชอบใช้ JSON, ไฟล์ csv หรือแม้แต่sqliteฐานข้อมูลเพื่อจัดเก็บ
nosklo

30

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

# yo dawg, i heard you liked dicts                                                                      
def yodict():
    return defaultdict(yodict)

2
@wberry: yodict = lambda: defaultdict(yodict)ที่จริงทั้งหมดที่คุณต้องการ
martineau

1
รุ่นที่ได้รับการยอมรับเป็นคลาสย่อยของdictเพื่อที่จะเทียบเท่าอย่างเต็มที่เราจะต้องx = Vdict(a=1, b=2)ทำงาน
wberry

@wberry: โดยไม่คำนึงถึงสิ่งที่อยู่ในคำตอบที่ยอมรับการเป็นคลาสย่อยdictไม่ใช่ข้อกำหนดที่ระบุไว้โดย OP ซึ่งเป็นผู้ถามเพียง "วิธีที่ดีที่สุด" ในการใช้พวกเขา - และนอกจากนั้นไม่ควร / ไม่ควรทำ มีความสำคัญมากในงูใหญ่อยู่ดี
martineau

24

คุณสามารถสร้างไฟล์ YAML และอ่านมันในการใช้PyYaml

ขั้นตอนที่ 1: สร้างไฟล์ YAML "Employment.yml":

new jersey:
  mercer county:
    pumbers: 3
    programmers: 81
  middlesex county:
    salesmen: 62
    programmers: 81
new york:
  queens county:
    plumbers: 9
    salesmen: 36

ขั้นตอนที่ 2: อ่านใน Python

import yaml
file_handle = open("employment.yml")
my_shnazzy_dictionary = yaml.safe_load(file_handle)
file_handle.close()

และตอนนี้my_shnazzy_dictionaryมีค่าทั้งหมดของคุณ หากคุณจำเป็นต้องทำเช่นนี้ได้ทันทีคุณสามารถสร้าง YAML yaml.safe_load(...)เป็นสตริงและอาหารที่เป็น


4
YAML เป็นตัวเลือกของฉันแน่นอนสำหรับการป้อนข้อมูลซ้อนกันจำนวนมาก (และไฟล์กำหนดค่า, การจำลองข้อมูล, ฯลฯ .. ) หาก OP ไม่ต้องการให้มีไฟล์พิเศษวางอยู่ให้ใช้สตริง Python ปกติในไฟล์บางไฟล์แล้วแยกวิเคราะห์ด้วย YAML
kmelvn

ข้อดีของการสร้างสตริง YAML: นี่จะเป็นวิธีที่สะอาดกว่าการใช้โมดูล "tempfile" ซ้ำ ๆ
Pete

18

เนื่องจากคุณมีการออกแบบสตาร์ - สคีมาคุณอาจต้องการจัดโครงสร้างให้เหมือนตารางเชิงสัมพันธ์มากกว่าและน้อยกว่าเช่นพจนานุกรม

import collections

class Jobs( object ):
    def __init__( self, state, county, title, count ):
        self.state= state
        self.count= county
        self.title= title
        self.count= count

facts = [
    Jobs( 'new jersey', 'mercer county', 'plumbers', 3 ),
    ...

def groupBy( facts, name ):
    total= collections.defaultdict( int )
    for f in facts:
        key= getattr( f, name )
        total[key] += f.count

สิ่งประเภทนั้นสามารถสร้างการออกแบบที่เหมือนคลังข้อมูลได้ไกลโดยไม่ต้องมีโอเวอร์เฮดของ SQL


14

หากจำนวนของระดับการซ้อนมีน้อยฉันใช้collections.defaultdictสิ่งนี้:

from collections import defaultdict

def nested_dict_factory(): 
  return defaultdict(int)
def nested_dict_factory2(): 
  return defaultdict(nested_dict_factory)
db = defaultdict(nested_dict_factory2)

db['new jersey']['mercer county']['plumbers'] = 3
db['new jersey']['mercer county']['programmers'] = 81

ใช้defaultdictเช่นนี้หลีกเลี่ยงการเป็นจำนวนมากยุ่งsetdefault(), get()ฯลฯ


+1: defaultdict เป็นหนึ่งในการเพิ่มรายการโปรดของฉันใน python ไม่มี. setdefault () อีกต่อไป!
John Fouhy

8

นี่คือฟังก์ชั่นที่ส่งคืนพจนานุกรมที่ซ้อนกันของความลึกตามอำเภอใจ:

from collections import defaultdict
def make_dict():
    return defaultdict(make_dict)

ใช้แบบนี้:

d=defaultdict(make_dict)
d["food"]["meat"]="beef"
d["food"]["veggie"]="corn"
d["food"]["sweets"]="ice cream"
d["animal"]["pet"]["dog"]="collie"
d["animal"]["pet"]["cat"]="tabby"
d["animal"]["farm animal"]="chicken"

วนซ้ำทุกสิ่งด้วยสิ่งนี้:

def iter_all(d,depth=1):
    for k,v in d.iteritems():
        print "-"*depth,k
        if type(v) is defaultdict:
            iter_all(v,depth+1)
        else:
            print "-"*(depth+1),v

iter_all(d)

สิ่งนี้พิมพ์ออกมา:

- food
-- sweets
--- ice cream
-- meat
--- beef
-- veggie
--- corn
- animal
-- pet
--- dog
---- labrador
--- cat
---- tabby
-- farm animal
--- chicken

ในที่สุดคุณอาจต้องการสร้างมันขึ้นมาเพื่อที่จะไม่สามารถเพิ่มไอเท็มใหม่ลงใน dict มันง่ายที่จะซ้ำแปลงทั้งหมดเหล่านี้defaultdictเพื่อปกติdicts

def dictify(d):
    for k,v in d.iteritems():
        if isinstance(v,defaultdict):
            d[k] = dictify(v)
    return dict(d)

7

ฉันพบว่าsetdefaultมีประโยชน์มาก มันจะตรวจสอบว่ามีคีย์และเพิ่มถ้าไม่:

d = {}
d.setdefault('new jersey', {}).setdefault('mercer county', {})['plumbers'] = 3

setdefaultส่งคืนคีย์ที่เกี่ยวข้องเสมอดังนั้นคุณจึงอัปเดตค่าของ " d" แทน

เมื่อพูดถึงการทำซ้ำฉันแน่ใจว่าคุณสามารถเขียนตัวสร้างได้ง่ายพอถ้าไม่มีอยู่ใน Python:

def iterateStates(d):
    # Let's count up the total number of "plumbers" / "dentists" / etc.
    # across all counties and states
    job_totals = {}

    # I guess this is the annoying nested stuff you were talking about?
    for (state, counties) in d.iteritems():
        for (county, jobs) in counties.iteritems():
            for (job, num) in jobs.iteritems():
                # If job isn't already in job_totals, default it to zero
                job_totals[job] = job_totals.get(job, 0) + num

    # Now return an iterator of (job, number) tuples
    return job_totals.iteritems()

# Display all jobs
for (job, num) in iterateStates(d):
    print "There are %d %s in total" % (job, num)

ฉันชอบโซลูชันนี้ แต่เมื่อฉันลอง: count.setdefault (a, {}). setdefault (b, {}). setdefault (c, 0) + = 1 ฉันได้รับ "นิพจน์ที่ผิดกฎหมายสำหรับการมอบหมายเพิ่มเติม"
dfrankow

6

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

import sqlite3

c = sqlite3.Connection(':memory:')
c.execute('CREATE TABLE jobs (state, county, title, count)')

c.executemany('insert into jobs values (?, ?, ?, ?)', [
    ('New Jersey', 'Mercer County',    'Programmers', 81),
    ('New Jersey', 'Mercer County',    'Plumbers',     3),
    ('New Jersey', 'Middlesex County', 'Programmers', 81),
    ('New Jersey', 'Middlesex County', 'Salesmen',    62),
    ('New York',   'Queens County',    'Salesmen',    36),
    ('New York',   'Queens County',    'Plumbers',     9),
])

# some example queries
print list(c.execute('SELECT * FROM jobs WHERE county = "Queens County"'))
print list(c.execute('SELECT SUM(count) FROM jobs WHERE title = "Programmers"'))

นี่เป็นเพียงตัวอย่างง่ายๆ คุณสามารถกำหนดตารางแยกต่างหากสำหรับรัฐมณฑลและตำแหน่งงาน


5

collections.defaultdictสามารถจัดเป็นประเภทย่อยเพื่อสร้าง dict ที่ซ้อนกัน จากนั้นเพิ่มวิธีการวนซ้ำที่มีประโยชน์ในคลาสนั้น

>>> from collections import defaultdict
>>> class nesteddict(defaultdict):
    def __init__(self):
        defaultdict.__init__(self, nesteddict)
    def walk(self):
        for key, value in self.iteritems():
            if isinstance(value, nesteddict):
                for tup in value.walk():
                    yield (key,) + tup
            else:
                yield key, value


>>> nd = nesteddict()
>>> nd['new jersey']['mercer county']['plumbers'] = 3
>>> nd['new jersey']['mercer county']['programmers'] = 81
>>> nd['new jersey']['middlesex county']['programmers'] = 81
>>> nd['new jersey']['middlesex county']['salesmen'] = 62
>>> nd['new york']['queens county']['plumbers'] = 9
>>> nd['new york']['queens county']['salesmen'] = 36
>>> for tup in nd.walk():
    print tup


('new jersey', 'mercer county', 'programmers', 81)
('new jersey', 'mercer county', 'plumbers', 3)
('new jersey', 'middlesex county', 'programmers', 81)
('new jersey', 'middlesex county', 'salesmen', 62)
('new york', 'queens county', 'salesmen', 36)
('new york', 'queens county', 'plumbers', 9)

1
นี่คือคำตอบที่ใกล้เคียงที่สุดกับสิ่งที่ฉันกำลังมองหา แต่ความนึกคิดจะมีฟังก์ชั่นผู้ช่วยทุกประเภทเช่น walk_keys () หรือเช่นนั้น ฉันประหลาดใจที่ไม่มีห้องสมุดมาตรฐานให้ทำเช่นนี้
YGA

4

สำหรับ "บล็อกลอง / จับที่น่ารังเกียจ":

d = {}
d.setdefault('key',{}).setdefault('inner key',{})['inner inner key'] = 'value'
print d

อัตราผลตอบแทน

{'key': {'inner key': {'inner inner key': 'value'}}}

คุณสามารถใช้สิ่งนี้เพื่อแปลงจากรูปแบบพจนานุกรมแฟลตของคุณเป็นรูปแบบที่มีโครงสร้าง:

fd = {('new jersey', 'mercer county', 'plumbers'): 3,
 ('new jersey', 'mercer county', 'programmers'): 81,
 ('new jersey', 'middlesex county', 'programmers'): 81,
 ('new jersey', 'middlesex county', 'salesmen'): 62,
 ('new york', 'queens county', 'plumbers'): 9,
 ('new york', 'queens county', 'salesmen'): 36}

for (k1,k2,k3), v in fd.iteritems():
    d.setdefault(k1, {}).setdefault(k2, {})[k3] = v


4

defaultdict() เป็นเพื่อนของคุณ!

สำหรับพจนานุกรมสองมิติที่คุณสามารถทำได้:

d = defaultdict(defaultdict)
d[1][2] = 3

สำหรับมิติข้อมูลเพิ่มเติมคุณสามารถ:

d = defaultdict(lambda :defaultdict(defaultdict))
d[1][2][3] = 4

คำตอบนี้ใช้ได้ผลแค่สามระดับเท่านั้น สำหรับระดับที่กำหนดเองพิจารณาคำตอบนี้
คิวเมนตัส

3

เพื่อความง่ายในการวนซ้ำในพจนานุกรมที่ซ้อนกันของคุณทำไมไม่เพียงแค่เขียนตัวสร้างแบบง่าย?

def each_job(my_dict):
    for state, a in my_dict.items():
        for county, b in a.items():
            for job, value in b.items():
                yield {
                    'state'  : state,
                    'county' : county,
                    'job'    : job,
                    'value'  : value
                }

ดังนั้นถ้าคุณมีพจนานุกรมที่ซ้อนกันที่คอมไพล์แล้วการวนซ้ำมันจะกลายเป็นเรื่องง่าย:

for r in each_job(my_dict):
    print "There are %d %s in %s, %s" % (r['value'], r['job'], r['county'], r['state'])

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

ทำไมคุณลองใช้ catch catch block อ่านต้นไม้? มันง่ายพอ (และอาจปลอดภัยกว่า) ในการสืบค้นว่ามีคีย์อยู่ใน dict หรือไม่ก่อนที่จะพยายามดึงมัน ฟังก์ชั่นที่ใช้คำสั่งป้องกันอาจมีลักษณะเช่นนี้:

if not my_dict.has_key('new jersey'):
    return False

nj_dict = my_dict['new jersey']
...

หรือวิธีการ verbose ค่อนข้างจะใช้วิธีการรับ:

value = my_dict.get('new jersey', {}).get('middlesex county', {}).get('salesmen', 0)

แต่สำหรับวิธีที่ค่อนข้างชัดเจนคุณอาจต้องการใช้คอลเล็กชัน. defaultdictซึ่งเป็นส่วนหนึ่งของไลบรารี่มาตรฐานตั้งแต่ไพ ธ อน 2.5

import collections

def state_struct(): return collections.defaultdict(county_struct)
def county_struct(): return collections.defaultdict(job_struct)
def job_struct(): return 0

my_dict = collections.defaultdict(state_struct)

print my_dict['new jersey']['middlesex county']['salesmen']

ฉันตั้งสมมติฐานเกี่ยวกับความหมายของโครงสร้างข้อมูลของคุณที่นี่ แต่ควรปรับได้ง่ายสำหรับสิ่งที่คุณต้องการทำ


2

ฉันชอบความคิดของการห่อหุ้มสิ่งนี้ในชั้นเรียนและการใช้งาน__getitem__และ__setitem__สิ่งที่พวกเขาใช้ภาษาแบบสอบถามอย่างง่าย:

>>> d['new jersey/mercer county/plumbers'] = 3
>>> d['new jersey/mercer county/programmers'] = 81
>>> d['new jersey/mercer county/programmers']
81
>>> d['new jersey/mercer country']
<view which implicitly adds 'new jersey/mercer county' to queries/mutations>

หากคุณต้องการจินตนาการคุณสามารถใช้สิ่งต่อไปนี้:

>>> d['*/*/programmers']
<view which would contain 'programmers' entries>

แต่ส่วนใหญ่ฉันคิดว่าสิ่งนี้จะสนุกจริง ๆ ที่จะใช้: D


ฉันคิดว่านี่เป็นความคิดที่ไม่ดี - คุณไม่สามารถทำนายไวยากรณ์ของคีย์ได้ คุณยังคงแทนที่getitemและsetitemแต่ให้พวกเขารับสิ่งอันดับ
YGA

3
@YGA คุณอาจพูดถูก แต่ก็สนุกที่จะคิดเกี่ยวกับการใช้ภาษาขนาดเล็กเช่นนี้
Aaron Maenpaa

1

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


1
class JobDb(object):
    def __init__(self):
        self.data = []
        self.all = set()
        self.free = []
        self.index1 = {}
        self.index2 = {}
        self.index3 = {}

    def _indices(self,(key1,key2,key3)):
        indices = self.all.copy()
        wild = False
        for index,key in ((self.index1,key1),(self.index2,key2),
                                             (self.index3,key3)):
            if key is not None:
                indices &= index.setdefault(key,set())
            else:
                wild = True
        return indices, wild

    def __getitem__(self,key):
        indices, wild = self._indices(key)
        if wild:
            return dict(self.data[i] for i in indices)
        else:
            values = [self.data[i][-1] for i in indices]
            if values:
                return values[0]

    def __setitem__(self,key,value):
        indices, wild = self._indices(key)
        if indices:
            for i in indices:
                self.data[i] = key,value
        elif wild:
            raise KeyError(k)
        else:
            if self.free:
                index = self.free.pop(0)
                self.data[index] = key,value
            else:
                index = len(self.data)
                self.data.append((key,value))
                self.all.add(index)
            self.index1.setdefault(key[0],set()).add(index)
            self.index2.setdefault(key[1],set()).add(index)
            self.index3.setdefault(key[2],set()).add(index)

    def __delitem__(self,key):
        indices,wild = self._indices(key)
        if not indices:
            raise KeyError
        self.index1[key[0]] -= indices
        self.index2[key[1]] -= indices
        self.index3[key[2]] -= indices
        self.all -= indices
        for i in indices:
            self.data[i] = None
        self.free.extend(indices)

    def __len__(self):
        return len(self.all)

    def __iter__(self):
        for key,value in self.data:
            yield key

ตัวอย่าง:

>>> db = JobDb()
>>> db['new jersey', 'mercer county', 'plumbers'] = 3
>>> db['new jersey', 'mercer county', 'programmers'] = 81
>>> db['new jersey', 'middlesex county', 'programmers'] = 81
>>> db['new jersey', 'middlesex county', 'salesmen'] = 62
>>> db['new york', 'queens county', 'plumbers'] = 9
>>> db['new york', 'queens county', 'salesmen'] = 36

>>> db['new york', None, None]
{('new york', 'queens county', 'plumbers'): 9,
 ('new york', 'queens county', 'salesmen'): 36}

>>> db[None, None, 'plumbers']
{('new jersey', 'mercer county', 'plumbers'): 3,
 ('new york', 'queens county', 'plumbers'): 9}

>>> db['new jersey', 'mercer county', None]
{('new jersey', 'mercer county', 'plumbers'): 3,
 ('new jersey', 'mercer county', 'programmers'): 81}

>>> db['new jersey', 'middlesex county', 'programmers']
81

>>>

แก้ไข:ตอนนี้กลับพจนานุกรมเมื่อสอบถามด้วยบัตรเสริม ( None) และค่าเดียวอื่น ๆ


ทำไมรายการกลับมา? ดูเหมือนว่ามันควรจะส่งคืนพจนานุกรม (เพื่อให้คุณรู้ว่าแต่ละหมายเลขหมายถึงอะไร) หรือผลรวม (เนื่องจากนั่นคือทั้งหมดที่คุณสามารถทำได้กับรายการ)
Ben Blank

0

ฉันมีสิ่งที่คล้ายกันเกิดขึ้น ฉันมีหลายกรณีที่ฉัน:

thedict = {}
for item in ('foo', 'bar', 'baz'):
  mydict = thedict.get(item, {})
  mydict = get_value_for(item)
  thedict[item] = mydict

แต่จะไปหลายระดับลึก มันคือ ".get (item, {})" ซึ่งเป็นกุญแจสำคัญในการสร้างพจนานุกรมใหม่หากยังไม่มีพจนานุกรม ในขณะเดียวกันฉันก็คิดถึงวิธีที่จะจัดการกับสิ่งที่ดีกว่านี้ ตอนนี้มีจำนวนมาก

value = mydict.get('foo', {}).get('bar', {}).get('baz', 0)

ดังนั้นฉันจึงทำ:

def dictgetter(thedict, default, *args):
  totalargs = len(args)
  for i,arg in enumerate(args):
    if i+1 == totalargs:
      thedict = thedict.get(arg, default)
    else:
      thedict = thedict.get(arg, {})
  return thedict

ซึ่งมีผลเหมือนกันถ้าคุณ:

value = dictgetter(mydict, 0, 'foo', 'bar', 'baz')

ดีขึ้นหรือไม่ ฉันคิดอย่างนั้น


0

คุณสามารถใช้การเรียกซ้ำใน lambdas และ defaultdict ไม่จำเป็นต้องกำหนดชื่อ:

a = defaultdict((lambda f: f(f))(lambda g: lambda:defaultdict(g(g))))

นี่คือตัวอย่าง:

>>> a['new jersey']['mercer county']['plumbers']=3
>>> a['new jersey']['middlesex county']['programmers']=81
>>> a['new jersey']['mercer county']['programmers']=81
>>> a['new jersey']['middlesex county']['salesmen']=62
>>> a
defaultdict(<function __main__.<lambda>>,
        {'new jersey': defaultdict(<function __main__.<lambda>>,
                     {'mercer county': defaultdict(<function __main__.<lambda>>,
                                  {'plumbers': 3, 'programmers': 81}),
                      'middlesex county': defaultdict(<function __main__.<lambda>>,
                                  {'programmers': 81, 'salesmen': 62})})})

0

ฉันเคยใช้ฟังก์ชั่นนี้ ปลอดภัยรวดเร็วบำรุงรักษาง่าย

def deep_get(dictionary, keys, default=None):
    return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)

ตัวอย่าง:

>>> from functools import reduce
>>> def deep_get(dictionary, keys, default=None):
...     return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)
...
>>> person = {'person':{'name':{'first':'John'}}}
>>> print (deep_get(person, "person.name.first"))
John
>>> print (deep_get(person, "person.name.lastname"))
None
>>> print (deep_get(person, "person.name.lastname", default="No lastname"))
No lastname
>>>
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.