Python: defaultdict ของ defaultdict หรือไม่


323

มีวิธีที่จะมีdefaultdict(defaultdict(int))เพื่อให้รหัสต่อไปนี้ทำงานได้หรือไม่

for x in stuff:
    d[x.a][x.b] += x.c_int

dจะต้องมีการสร้างโฆษณาขึ้นอยู่กับองค์ประกอบx.aและx.b

ฉันสามารถใช้:

for x in stuff:
    d[x.a,x.b] += x.c_int

แต่ฉันจะไม่สามารถใช้:

d.keys()
d[x.a].keys()

6
ดูคำถามที่คล้ายกันวิธีที่ดีที่สุดในการใช้พจนานุกรมแบบซ้อนใน Python คืออะไร . นอกจากนี้ยังมีบางข้อมูลที่เป็นประโยชน์อาจจะเป็นในบทความวิกิพีเดียในAutovivification
martineau

คำตอบ:


571

ใช่เช่นนี้:

defaultdict(lambda: defaultdict(int))

อาร์กิวเมนต์ของdefaultdict(ในกรณีนี้lambda: defaultdict(int)) จะถูกเรียกเมื่อคุณพยายามเข้าถึงคีย์ที่ไม่มีอยู่ ค่าตอบแทนของมันจะถูกกำหนดให้เป็นค่าใหม่ของคีย์นี้ซึ่งหมายความว่าในกรณีของเราค่าของจะd[Key_doesnt_exist]defaultdict(int)

ถ้าคุณพยายามที่จะเข้าถึงคีย์จาก defaultdict สุดท้ายนี้คือd[Key_doesnt_exist][Key_doesnt_exist]มันจะกลับ 0 ซึ่งเป็นค่าตอบแทนของการโต้แย้งของสุดท้าย defaultdict int()คือ


7
มันใช้งานได้ดี! คุณสามารถอธิบายเหตุผลที่อยู่เบื้องหลังไวยากรณ์นี้ได้หรือไม่
Jonathan

37
@ โจนาธาน: ใช่แน่นอนจะเรียกอาร์กิวเมนต์ของdefaultdict(ในกรณีนี้lambda : defaultdict(int)) เมื่อคุณพยายามเข้าถึงคีย์ที่ไม่มีอยู่และค่าส่งคืนของมันจะถูกตั้งค่าเป็นค่าใหม่ของคีย์นี้ซึ่งหมายถึง กรณีของเราคือค่าของd[Key_dont_exist]จะdefaultdict(int)และถ้าคุณพยายามที่จะเข้าถึงกุญแจจาก defaultdict ล่าสุดนี้d[Key_dont_exist][Key_dont_exist]มันจะกลับ 0 ซึ่งเป็นค่าตอบแทนของอาร์กิวเมนต์ของสุดท้ายdefaultdictคือint()หวังว่ามันจะเป็นประโยชน์
mouad

25
อาร์กิวเมนต์ที่defaultdictควรเป็นฟังก์ชัน defaultdict(int)เป็นพจนานุกรมในขณะที่lambda: defaultdict(int)เป็นฟังก์ชันที่ส่งคืนพจนานุกรม
has2k1

27
@ has2k1 นั่นไม่ถูกต้อง อาร์กิวเมนต์ของ defaultdict ต้องเป็น callable แลมบ์ดาเรียกได้
Niels Bom

2
@RickyLevi ถ้าคุณต้องการทำงานแบบนั้นคุณสามารถพูดได้ว่า: defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
darophi

51

พารามิเตอร์ของตัวสร้าง defaultdict เป็นฟังก์ชันที่จะถูกเรียกสำหรับการสร้างองค์ประกอบใหม่ ดังนั้นลองใช้แลมบ์ดากัน!

>>> from collections import defaultdict
>>> d = defaultdict(lambda : defaultdict(int))
>>> print d[0]
defaultdict(<type 'int'>, {})
>>> print d[0]["x"]
0

ตั้งแต่ Python 2.7 มีวิธีแก้ปัญหาที่ดียิ่งขึ้นเมื่อใช้ Counter :

>>> from collections import Counter
>>> c = Counter()
>>> c["goodbye"]+=1
>>> c["and thank you"]=42
>>> c["for the fish"]-=5
>>> c
Counter({'and thank you': 42, 'goodbye': 1, 'for the fish': -5})

คุณสมบัติโบนัสบางอย่าง

>>> c.most_common()[:2]
[('and thank you', 42), ('goodbye', 1)]

สำหรับข้อมูลเพิ่มเติมโปรดดูPyMOTW - ชุดรวม - ประเภทข้อมูลคอนเทนเนอร์และเอกสาร Python - ชุด


5
เพียงกรอกวงกลมให้ครบที่นี่คุณต้องการใช้d = defaultdict(lambda : Counter())แทนที่จะd = defaultdict(lambda : defaultdict(int))แก้ปัญหาเฉพาะที่โพสต์ไว้
gumption

3
@ gumption คุณสามารถใช้d = defaultdict(Counter())แลมบ์ดาได้ในกรณีนี้
Deb

3
@Deb คุณมีข้อผิดพลาดเล็กน้อย - ลบวงเล็บภายในเพื่อให้คุณผ่าน callable แทนCounterวัตถุ นั่นคือ:d = defaultdict(Counter)
Dillon Davis

29

ฉันคิดว่ามันใช้งานได้ดีกว่าเล็กน้อยpartial:

import functools
dd_int = functools.partial(defaultdict, int)
defaultdict(dd_int)

แน่นอนว่านี่เป็นแลมบ์ดา


1
บางส่วนยังดีกว่าแลมบ์ดาที่นี่เพราะมันสามารถนำไปใช้ซ้ำได้ :) ดูคำตอบของฉันด้านล่างสำหรับวิธีการแบบ defaultdict โรงงานแบบซ้อนทั่วไป
Campi

@Campi คุณไม่ต้องการบางส่วนสำหรับแอปพลิเคชันแบบเรียกซ้ำ AFAICT
Clément

10

สำหรับการอ้างอิงเป็นไปได้ที่จะใช้วิธีการซ้อนกันของdefaultdictโรงงานทั่วไปผ่าน

from collections import defaultdict
from functools import partial
from itertools import repeat


def nested_defaultdict(default_factory, depth=1):
    result = partial(defaultdict, default_factory)
    for _ in repeat(None, depth - 1):
        result = partial(defaultdict, result)
    return result()

ความลึกกำหนดจำนวนพจนานุกรมซ้อนกันก่อนประเภทที่กำหนดไว้ในdefault_factoryถูกนำมาใช้ ตัวอย่างเช่น:

my_dict = nested_defaultdict(list, 3)
my_dict['a']['b']['c'].append('e')

คุณสามารถยกตัวอย่างการใช้งานได้หรือไม่? ไม่ทำงานอย่างที่ฉันคาดไว้ ndd = nested_defaultdict(dict) .... ndd['a']['b']['c']['d'] = 'e'พ่นKeyError: 'b'
David Marx

เฮ้เดวิดคุณต้องกำหนดความลึกของพจนานุกรมของคุณในตัวอย่างที่ 3 ของคุณ (ตามที่คุณกำหนด default_factory ให้เป็นพจนานุกรมด้วยเช่นกัน nested_defaultdict (dict, 3) จะทำงานให้คุณ
Campi

นี่เป็นประโยชน์อย่างมากขอบคุณ! สิ่งหนึ่งที่ฉันสังเกตเห็นคือสิ่งนี้สร้าง default_dict ที่depth=0ซึ่งอาจไม่ต้องการเสมอหากไม่ทราบความลึกในขณะที่โทร แก้ไขได้อย่างง่ายดายโดยการเพิ่มบรรทัดif not depth: return default_factory()ที่ด้านบนของฟังก์ชั่นแม้ว่าอาจจะมีทางออกที่ดีกว่า
เบรนแดน

9

คำตอบก่อนหน้านี้ได้รับการแก้ไขวิธีที่จะทำให้สองระดับหรือ defaultdictn-ระดับ ในบางกรณีคุณต้องการอนันต์:

def ddict():
    return defaultdict(ddict)

การใช้งาน:

>>> d = ddict()
>>> d[1]['a'][True] = 0.5
>>> d[1]['b'] = 3
>>> import pprint; pprint.pprint(d)
defaultdict(<function ddict at 0x7fcac68bf048>,
            {1: defaultdict(<function ddict at 0x7fcac68bf048>,
                            {'a': defaultdict(<function ddict at 0x7fcac68bf048>,
                                              {True: 0.5}),
                             'b': 3})})

1
ฉันรักสิ่งนี้. มันง่ายมาก แต่มีประโยชน์อย่างเหลือเชื่อ ขอบคุณ!
rosstex

6

คนอื่น ๆ ตอบคำถามของคุณอย่างถูกต้องถึงวิธีการทำงานดังต่อไปนี้:

for x in stuff:
    d[x.a][x.b] += x.c_int

อีกทางเลือกหนึ่งคือใช้สิ่งอันดับสำหรับคีย์:

d = defaultdict(int)
for x in stuff:
    d[x.a,x.b] += x.c_int
    # ^^^^^^^ tuple key

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


4
วิธีการแก้ปัญหานี้หมายความว่ามันไม่ง่ายเลยที่จะได้รับ d [xa] ทั้งหมดเนื่องจากคุณต้องใคร่ครวญทุกคีย์เพื่อดูว่ามันมี xa เป็นองค์ประกอบแรกของ tuple หรือไม่
Matthew Schinckel

5
หากคุณต้องการทำรังลึก 3 ระดับเพียงกำหนดเป็น 3 ระดับ: d = defaultdict (lambda: defaultdict (lambda: defaultdict (int)))
Matthew Schinckel
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.