Python:defaultdictのdefaultdict?


323

defaultdict(defaultdict(int))次のコードを機能させるためにを持っている方法はありますか?

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

dx.aおよびx.b要素に応じて、アドホックで構築する必要があります。

私は使うことができます:

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

しかし、それから私は使うことができません:

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

6
同様の質問を見るPythonでネストされた辞書を実装する最良の方法は何ですか?。WikipediaのAutovivificationに関する記事には、おそらく役立つ情報もいくつかあります。
martineau 2014年

回答:


571

はい、このように:

defaultdict(lambda: defaultdict(int))

aの引数defaultdict(この場合はlambda: defaultdict(int))は、存在しないキーにアクセスしようとすると呼び出されます。その戻り値はこのキーの新しい値として設定されd[Key_doesnt_exist]ますdefaultdict(int)。つまり、この場合、の値はになります。

この最後のdefaultdictからキーにアクセスしようとすると、つまりd[Key_doesnt_exist][Key_doesnt_exist]0が返されます。これは、最後のdefaultdictの引数の戻り値ですint()


7
それはうまくいきます!この構文の背後にある合理性を説明できますか?
ジョナサン

37
@ジョナサン:はい、確かに、存在しないキーにアクセスしようとするとdefaultdict(この場合はlambda : defaultdict(int))の引数が呼び出され、その戻り値はこのキーの新しい値として設定されます。今回の場合、の値はにd[Key_dont_exist]なりますdefaultdict(int)。この最後のdefaultdictからキーにアクセスしようとするd[Key_dont_exist][Key_dont_exist]と、defaultdictつまりint()、最後のieの引数の戻り値である0が返されます。これが役に立ったと思います。
mouad

25
への引数defaultdictは関数でなければなりません。defaultdict(int)一方で、辞書でlambda: defaultdict(int)辞書を返す関数です。
has2k1 2012

27
@ has2k1不正解です。defaultdictの引数は呼び出し可能である必要があります。ラムダは呼び出し可能です。
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))
ガンプション2014年

3
@gumption d = defaultdict(Counter())この場合、ラムダは必要ありません
Deb

3
@Debには少しエラーがありCounterます。内側の括弧を削除して、オブジェクトではなく呼び出し可能オブジェクトを渡します。つまり:d = defaultdict(Counter)
ディロンデービス

29

私はそれを使用する方が少しエレガントだと思いますpartial

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

もちろん、これはラムダと同じです。


1
部分的には、再帰的に適用できるため、ここではラムダよりも優れています:)一般的なネストされたdefaultdictファクトリメソッドについては、以下の私の回答を参照してください。
Campi

@Campiあなたは再帰的な用途、AFAICTのための部分的な必要はありません
クレマン

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'
デビッドマルクス

。ねえダビデは、あなたがあなたの例では3(あなたも辞書であることをdefault_factoryを定義されているようnested_defaultdict(辞書、3)はあなたのために働くだろう、あなたの辞書の深さを定義する必要があります。
カンピ

これはとても役に立ちました、ありがとう!私が気づいたことの1つは、これがにdefault_dictを作成することです。これは、depth=0呼び出し時に深さが不明である場合、必ずしも望ましいとは限りません。if not depth: return default_factory()関数の上部に行を追加することで簡単に修正できますが、おそらくよりエレガントな解決策があります。
ブレンダン

9

以前の回答では、2レベルまたはnレベルを作成する方法について説明しましたdefaultdict。場合によっては、無限のものが必要です。

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

このアプローチの良い点は、シンプルで簡単に拡張できることです。3レベルの深さのマッピングが必要な場合は、キーに3項目のタプルを使用してください。


4
このソリューションは、すべてのキーをイントロスペクトしてタプルの最初の要素としてxaがあるかどうかを確認する必要があるため、d [xa]のすべてを取得することは簡単ではないことを意味します。
Matthew Schinckel、2011

5
3レベルの深さを入れ子にしたい場合は、それを3レベルとして定義します。d = defaultdict(lambda:defaultdict(lambda:defaultdict(int)))
Matthew Schinckel
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.