Nested Defaultdict of Defaultdict

Nested defaultdict of defaultdict

For an arbitrary number of levels:

def rec_dd():
return defaultdict(rec_dd)

>>> x = rec_dd()
>>> x['a']['b']['c']['d']
defaultdict(<function rec_dd at 0x7f0dcef81500>, {})
>>> print json.dumps(x)
{"a": {"b": {"c": {"d": {}}}}}

Of course you could also do this with a lambda, but I find lambdas to be less readable. In any case it would look like this:

rec_dd = lambda: defaultdict(rec_dd)

Nested `defaultdict of defaultdict of defaultdict` each with a backreference

Solution 1

Giving the same {BACKREF: node} to defaultdict:

from collections import defaultdict

BACKREF, END = 'BACKREF', '$'
words = ['hi', 'hello', 'hiya', 'hey']

tree = lambda: defaultdict(tree, {BACKREF: node})
node = None
root = tree()
for word in words:
node = root
for ch in word:
node = node[ch]
node[END] = None

The root node has a backref None, can be deleted if bothersome.

Solution 2

The above works fine if that code is the only code that creates tree nodes (seems likely to me, judging by the times I've built such trees myself). Otherwise you'd need to ensure that node points to the correct parent node. If that's an issue, here's an alternative with a dict (not defaultdict) subclass that implements __missing__ to automatically create children with backrefs when needed:

BACKREF, END = 'BACKREF', '$'
words = ['hi', 'hello', 'hiya', 'hey']

class Tree(dict):
def __missing__(self, key):
child = self[key] = Tree({BACKREF: self})
return child

root = Tree()
for word in words:
node = root
for ch in word:
node = node[ch]
node[END] = None

Also doesn't give the root a backref, and being a dict, its string representations are far less cluttered than a defaultdict's and thus far more readable:

>>> import pprint
>>> pprint.pp(root)
{'h': {'BACKREF': <Recursion on Tree with id=2494556270320>,
'i': {'BACKREF': <Recursion on Tree with id=2494556270400>,
'$': None,
'y': {'BACKREF': <Recursion on Tree with id=2494556270480>,
'a': {'BACKREF': <Recursion on Tree with id=2494556340608>,
'$': None}}},
'e': {'BACKREF': <Recursion on Tree with id=2494556270400>,
'l': {'BACKREF': <Recursion on Tree with id=2494556340288>,
'l': {'BACKREF': <Recursion on Tree with id=2494556340368>,
'o': {'BACKREF': <Recursion on Tree with id=2494556340448>,
'$': None}}},
'y': {'BACKREF': <Recursion on Tree with id=2494556340288>,
'$': None}}}}

The defaultdict result for comparison:

>>> pprint.pp(root)
defaultdict(<function <lambda> at 0x000001A13760BE50>,
{'BACKREF': None,
'h': defaultdict(<function <lambda> at 0x000001A13760BE50>,
{'BACKREF': <Recursion on defaultdict with id=1791930855152>,
'i': defaultdict(<function <lambda> at 0x000001A13760BE50>,
{'BACKREF': <Recursion on defaultdict with id=1791930855312>,
'$': None,
'y': defaultdict(<function <lambda> at 0x000001A13760BE50>,
{'BACKREF': <Recursion on defaultdict with id=1791930912832>,
'a': defaultdict(<function <lambda> at 0x000001A13760BE50>,
{'BACKREF': <Recursion on defaultdict with id=1791930913232>,
'$': None})})}),
'e': defaultdict(<function <lambda> at 0x000001A13760BE50>,
{'BACKREF': <Recursion on defaultdict with id=1791930855312>,
'l': defaultdict(<function <lambda> at 0x000001A13760BE50>,
{'BACKREF': <Recursion on defaultdict with id=1791930912912>,
'l': defaultdict(<function <lambda> at 0x000001A13760BE50>,
{'BACKREF': <Recursion on defaultdict with id=1791930912992>,
'o': defaultdict(<function <lambda> at 0x000001A13760BE50>,
{'BACKREF': <Recursion on defaultdict with id=1791930913072>,
'$': None})})}),
'y': defaultdict(<function <lambda> at 0x000001A13760BE50>,
{'BACKREF': <Recursion on defaultdict with id=1791930912912>,
'$': None})})})})

defaultdict of defaultdict?

Yes like this:

defaultdict(lambda: defaultdict(int))

The argument of a defaultdict (in this case is lambda: defaultdict(int)) will be called when you try to access a key that doesn't exist. The return value of it will be set as the new value of this key, which means in our case the value of d[Key_doesnt_exist] will be defaultdict(int).

If you try to access a key from this last defaultdict i.e. d[Key_doesnt_exist][Key_doesnt_exist] it will return 0, which is the return value of the argument of the last defaultdict i.e. int().

Nested Defaultdicts in Python

You didn’t nest any defaultdicts, so do that:

nested_dict = defaultdict(lambda: defaultdict(list))

and

nested_dict[keyword][feature].append(pos)

How to default a nested defaultdict() to a list of specified length?

You can specify that you want a default value of a defaultdict that has a default value of [0, 0, 0]

from collections import defaultdict

dic01 = defaultdict(lambda: defaultdict(lambda: [0, 0, 0]))

dic01['year2018']['jul_to_sep_temperature'][0] = 25

print(dic01)

prints

defaultdict(<function <lambda> at 0x7f4dc28ac598>, {'year2018': defaultdict(<function <lambda>.<locals>.<lambda> at 0x7f4dc28ac510>, {'jul_to_sep_temperature': [25, 0, 0]})})

Which you can treat as a regular nested dictionary

python3 additional problem with nested default dict

First of all, we need to understand why this is happening and what are we going to sacrifice by fixing this error:

TypeError: unsupported operand type(s) for +=: 'collections.defaultdict' and 'int'

This error tells you that x['a']['b']['c']['e'] += 2 is adding two to a default dictionary. This is obviously happening because the defaultdict sets all new elements to defaultdict. This would allow you to do something like:

x['a']['b']['c']['e']['another_one']

That wouldn't be a problem because just like you've seen from your error, the element at ['e'] is a default dictionary allowing you to further index it with something like ['another_one']. Now we can change your code to make the dictionary have integers at level four, but you would be sacrificing the ability to further index the dictionary to levels beyond 4:

Solution 1 (?)

from functools import partial

def rec_dd(depth=0):
if depth == 4:
return 0

return defaultdict(partial(rec_dd, depth + 1))

The function above returns a default dictionary until you reach level 4 at which you want to have integers instead.

Now let's take a look at the modified function above, it stops at depth 4 resulting in the following behavior:

x['a']['b']['g'] = 2

Will work without any problems, because you're setting the dictionary at depth 3 to an int.

x['a']['b']['h'] + 3

Will not work because everything up to depth 4 is dictionary and you're trying to add an int to a dictionary.

x['a']['b']['c']['d'] = 2
x['a']['b']['c']['e'] += 2

Both of those options will work because the level 4 now contains ints and not dictionaries. HOWEVER:

x['a']['b']['c']['e']['f'] = 2

You sacrifice the ability to index past level 4 because level 4 now has ints and ints are not subscriptable.

This solution would totally fix the example that you have provided but might not be the optimal solution if you want customizable depth. Luckily for you, there are already a lot of very high quality posts showing how to implement a nested dictionary where you can add any behavior to your nested container:

What is the best way to implement nested dictionaries?

I recommend reading through that first if you want further functionality.

defaultdict of defaultdict of int

Try this:

from collections import defaultdict

m = defaultdict(lambda: defaultdict(int))
m['a']['b'] += 1

Note if you want more depth, you can still use a recursive approach:

def ddict(some_type, depth=0):
if depth == 0:
return defaultdict(some_type)
else:
return defaultdict(lambda: ddict(some_type, depth-1))

m = ddict(int, depth=2)
m['a']['b']['c'] += 1

Python Defaultdict with defined dictionary as default value shares the same dictionary between keys

To fix this, you simply need to move your counter_format dictionary construction into the lambda, so that a new counter_format dictionary is created each time you try to access a missing value in your innermost defaultdict

from collections import defaultdict

userDict = defaultdict(lambda:
defaultdict(lambda: {"correct": 0, "incorrect": 0})
)

userDict['a'][1]['correct']+=1
userDict['b'][1]['correct']+=4
userDict['b'][2]['correct']+=1

print(userdict)
defaultdict(<function <lambda> at 0x7f0f6cb38550>,
{'a': defaultdict(<function <lambda>.<locals>.<lambda> at 0x7f0f679424c0>,
{1: {'correct': 1, 'incorrect': 0}}),
'b': defaultdict(<function <lambda>.<locals>.<lambda> at 0x7f0f4caaa430>,
{1: {'correct': 4, 'incorrect': 0},
2: {'correct': 1, 'incorrect': 0}})})



Related Topics



Leave a reply



Submit