Python: defaultdict của defaultdict?


323

Có cách nào để có một defaultdict(defaultdict(int))đoạn mã sau hoạt động không?

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

dcần phải được xây dựng đặc biệt, tùy thuộc x.ax.bcác yếu tố.

Tôi có thể sử dụng:

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

nhưng sau đó tôi sẽ không thể sử dụng:

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

6
Xem câu hỏi tương tự Cách tốt nhất để triển khai từ điển lồng nhau trong Python là gì? . Ngoài ra còn có một số thông tin hữu ích trong bài viết của Wikipedia về Tự động hóa .
martineau

Câu trả lời:


571

Có như thế này:

defaultdict(lambda: defaultdict(int))

Đối số của một defaultdict(trong trường hợp này là lambda: defaultdict(int)) sẽ được gọi khi bạn cố gắng truy cập khóa không tồn tại. Giá trị trả về của nó sẽ được đặt là giá trị mới của khóa này, có nghĩa là trong trường hợp của chúng tôi, giá trị của d[Key_doesnt_exist]sẽ là defaultdict(int).

Nếu bạn cố gắng truy cập một khóa từ defaultdict cuối cùng này, tức là d[Key_doesnt_exist][Key_doesnt_exist]nó sẽ trả về 0, đó là giá trị trả về của đối số của defaultdict cuối cùng tức là int().


7
nó hoạt động rất tốt bạn có thể giải thích sự hợp lý đằng sau cú pháp này?
Jonathan

37
@Jonathan: Có chắc chắn, đối số của defaultdict(trong trường hợp này là lambda : defaultdict(int)) sẽ được gọi khi bạn cố truy cập khóa không tồn tại và giá trị trả về của nó sẽ được đặt thành giá trị mới của khóa này có nghĩa là trường hợp của chúng tôi giá trị của d[Key_dont_exist]sẽ defaultdict(int), và nếu bạn cố gắng truy cập vào một phím từ defaultdict cuối cùng này có nghĩa là d[Key_dont_exist][Key_dont_exist]nó sẽ trở về 0 là giá trị trả về của các đối số của người cuối cùng defaultdicttức là int(), Hy vọng điều này là hữu ích.
mouad

25
Đối số defaultdictphải là một hàm. defaultdict(int)là một từ điển, trong khi lambda: defaultdict(int)là chức năng trả về một từ điển.
has2k1

27
@ has2k1 Điều đó không chính xác. Đối số để defaultdict cần phải có thể gọi được. Một lambda là một cuộc gọi.
Niels Bom

2
@RickyLevi, nếu bạn muốn làm việc đó, bạn chỉ cần nói: defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
darophi 26/03/19

51

Tham số cho hàm tạo defaultdict là hàm sẽ được gọi để xây dựng các phần tử mới. Vì vậy, hãy sử dụng lambda!

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

Kể từ Python 2.7, có một giải pháp thậm chí tốt hơn bằng cách sử dụng 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})

Một số tính năng thưởng

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

Để biết thêm thông tin, hãy xem PyMOTW - Bộ sưu tập - Kiểu dữ liệu chứaTài liệu Python - bộ sưu tập


5
Chỉ cần hoàn thành vòng tròn ở đây, bạn sẽ muốn sử dụng d = defaultdict(lambda : Counter())hơn là d = defaultdict(lambda : defaultdict(int))để giải quyết cụ thể vấn đề như được đặt ra ban đầu.
cách giao thiệp

3
@greas bạn chỉ có thể sử dụng d = defaultdict(Counter())không cần lambda trong trường hợp này
Deb

3
@Deb bạn có một lỗi nhỏ - loại bỏ các dấu ngoặc đơn bên trong để bạn vượt qua một cuộc gọi thay vì một Counterđối tượng. Đó là:d = defaultdict(Counter)
Dillon Davis

29

Tôi thấy nó thanh lịch hơn một chút để sử dụng partial:

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

Tất nhiên, điều này giống như lambda.


1
Một phần cũng tốt hơn lambda ở đây vì nó có thể được áp dụng đệ quy :) xem câu trả lời của tôi dưới đây cho một phương thức nhà máy defaultdict lồng nhau chung.
Campi

@Campi bạn không cần một phần cho các ứng dụng đệ quy, AFAICT
Clément

10

Để tham khảo, có thể thực hiện defaultdictphương pháp nhà máy lồng nhau thông qua:

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

Độ sâu xác định số lượng từ điển lồng nhau trước khi loại được xác định trong default_factoryđược sử dụng. Ví dụ:

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

Bạn có thể đưa ra một ví dụ sử dụng? Không làm việc theo cách tôi mong đợi này. ndd = nested_defaultdict(dict) .... ndd['a']['b']['c']['d'] = 'e'némKeyError: 'b'
David Marx

Này David, bạn cần xác định độ sâu của từ điển, trong ví dụ 3 của bạn (như bạn đã xác định default_factory cũng là một từ điển. Nested_defaultdict (dict, 3) sẽ hoạt động cho bạn.
Campi

Điều này là siêu hữu ích, cảm ơn! Một điều tôi nhận thấy là điều này tạo ra default_dict tại depth=0, điều này có thể không phải lúc nào cũng được mong muốn nếu độ sâu không xác định tại thời điểm gọi. Có thể dễ dàng sửa chữa bằng cách thêm một dòng if not depth: return default_factory(), ở đầu chức năng, mặc dù có lẽ có một giải pháp thanh lịch hơn.
Brendan

9

Các câu trả lời trước đã đề cập đến cách tạo một cấp độ hai hoặc cấp độ n defaultdict. Trong một số trường hợp bạn muốn một cái vô hạn:

def ddict():
    return defaultdict(ddict)

Sử dụng:

>>> 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
Tôi thích điều này. Nó cực kỳ đơn giản, nhưng vô cùng hữu ích. Cảm ơn!
rosstex

6

Những người khác đã trả lời chính xác câu hỏi của bạn về cách làm cho những điều sau đây hoạt động:

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

Một cách khác là sử dụng bộ dữ liệu cho các phím:

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

Điều hay ho của phương pháp này là nó đơn giản và có thể dễ dàng mở rộng. Nếu bạn cần ánh xạ ba cấp độ sâu, chỉ cần sử dụng bộ ba mục cho khóa.


4
Giải pháp này có nghĩa là không đơn giản để có được tất cả d [xa], vì bạn cần phải xem xét mọi khóa để xem liệu nó có xa như là yếu tố đầu tiên của bộ dữ liệu hay không.
Matthew Schinckel

5
Nếu bạn muốn lồng sâu 3 cấp độ, thì chỉ cần xác định nó là 3 cấp độ: d = defaultdict (lambda: defaultdict (lambda: defaultdict (int)))
Matthew Schinckel
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.