Associating string keys with int in nested dictionary - python

I'm trying to associate keys with a unique identifier. That is, transform dict1 to dict2:
dict1={'A': {'A': 1},
'B': {'B': .5, 'C': .36, 'E': .14},
'C': {'A': .5, 'C': .5},
'D': {'G': 1},
'E': {'F': 1},
'F': {}
}
dict2={1: {1: 1},
2: {2: .5, 3: .36, 5: .14},
3: {1: .5, 3: .5},
4: {7: 1},
5: {6: 1},
6: {}
}
I came up with something recursively but my code isn't working too well for nested keys. Any suggestions on how to fix the code or approach this problem?
def transform(d, count = 1):
output={}
for k,v in d.iteritems():
k=count
count = count + 1
if isinstance(v,dict):
v=transform(v, count)
output[k]=v
return output

You are missing a few parts. First of all, you need to pass any conversion that you have already determined (e.g. A = 1) to your function when you call it recursively - otherwise you won't use the same replacement for the same key in the nested dictionaries. Also, you need some way to ensure that when you generate a new key, that key is used up and won't be used again. When you increment count in your function, this will only affect it within the current call to the function - any calls higher up the chain will keep using a lower count, and so keys will be used multiple times.
My attempt:
import itertools
def transform(d, key_generator=None, conversion=None):
if key_generator is None:
key_generator = itertools.count(start=1)
if conversion is None:
conversion = {}
output = {}
for k, v in d.iteritems():
if k in conversion:
k = conversion[k]
else:
next_key = next(key_generator)
conversion[k] = next_key
k = next_key
if isinstance(v, dict):
v = transform(v, key_generator, conversion)
output[k] = v
return output
Testing:
conversion = {}
transform(dict1, conversion=conversion)
print conversion
{1: {1: 1},
2: {1: 0.5, 2: 0.5},
3: {2: 0.36, 3: 0.5, 4: 0.14},
4: {5: 1},
5: {},
6: {7: 1}}
{'A': 1, 'C': 2, 'B': 3, 'E': 4, 'D': 6, 'G': 7, 'F': 5}
Because of the undetermined iteration order of the dictionaries (and because, even if you sort the initial dictionary, E will be handled before D), this conversion is not quite what you were looking for, but it's pretty close.

Related

Bug in recursive dict-merging function

I want to merge inner dictionaries under a specific key (let's say 'x') into the outer dictionary. Lists, dicts and tuples should be traversed recursively.
For example:
>>> key_upmerge({'x': {1: 2, 3: 4}}, 'x')
{1: 2, 3: 4}
>>> key_upmerge({'x': {1: 2, 'x': {3: 4}}}, 'x')
{1: 2, 3: 4}
>>> key_upmerge({'x': {1: 2}, 2: 3}, 'x')
{1: 2, 2: 3}
>>> key_upmerge({'x': {1: 2, 'x': {1: 3}}}, 'x')
[...]
ValueError (clashing keys)
>>> key_upmerge([{'x': {1: 2}}, {'x': {3: 4}}], 'x')
[{1: 2}, {3: 4}]
My function already works for all the test cases above.
def key_upmerge(d, key):
if isinstance(d, (list, tuple)):
result = type(d)(key_upmerge(x, key) for x in d)
elif isinstance(d, dict):
result = {}
for k, v in d.items():
# NOTE: only need to check type of v. k cannot contain dicts
if isinstance(v, (dict, list, tuple)):
v = key_upmerge(v, key)
if k == key and isinstance(v, dict):
if both := (result.keys() & v.keys()):
msg = f'Cannot merge dict into upper dict: clashing keys {both}'
raise ValueError(msg)
result.update(v)
else:
result[k] = v
else:
raise TypeError('expected dict, list or tuple')
return result
The problem is that I also expected a ValueError for the following test cases, but the function returns a result.
>>> key_upmerge({'x': {1: 2}, 1: 3}, 'x')
{1: 3} # expected ValueError due to clashing keys
>>> key_upmerge({'x': {1: 2}, 1: {'x': {3: 4}}}, 'x')
{1: {3: 4}} # expected ValueError due to clashing keys
You build the result dict in order. You only check for key clashes when merging in a dict, not when assigning individual values to a key. Since your test cases always have the merged dict appear first ('x': {1, 2} precedes 1: 3), the clashing key doesn't exist in the result yet, and no clash is detected. Your test works only when the clashing individual key precedes the dict to be merged (key_upmerge({1: 3, 'x': {1: 2}}, 'x') dies as you expect).
To catch cases where a clashing individual key-value pair is inserted after a merge, replace:
else:
result[k] = v
with:
elif k in result:
raise ValueError(f'clashing key {k!r}')
else:
result[k] = v
verifying that k is not in result when you insert individual key-value pairs, not just when you perform bulk merges.

How to convert a three level nested dict into a three level of nested defaultdict?

I have converted a nested defaultdict in the form of defaultdict(lambda: defaultdict(lambda: defaultdict(int))) into a nested dict. How can I converted back into the same format and datatype?
you could create the target defaultdict structure beforehand and use a recursive function to copy the data in it:
from collections import defaultdict
def copyToDefaultdict(source,target):
for k,v in source.items():
if isinstance(v,dict) and isinstance(target[k],defaultdict):
copyToDefaultdict(v,target[k])
else:
target[k] = v
output:
target = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
source = { "a": { 10: { 'x':2, 'y':3}, 12:{ 'z':4, 'w':5} },
"b": { 20: { 'p':6, 'q':7}, 22:{ 'r':8, 's':9} } }
copyToDefaultdict(source,target)
target
defaultdict(<function <lambda> at 0x7fd9135d01e0>,
{'a': defaultdict(<function <lambda>.<locals>.<lambda> at 0x7fd914f9a510>,
{10: defaultdict(<class 'int'>, {'x': 2, 'y': 3}),
12: defaultdict(<class 'int'>, {'z': 4, 'w': 5})
}),
'b': defaultdict(<function <lambda>.<locals>.<lambda> at 0x7fd914f9a598>,
{20: defaultdict(<class 'int'>, {'p': 6, 'q': 7}),
22: defaultdict(<class 'int'>, {'r': 8, 's': 9})
})
})
You can write a recursive function for that, but the tricky part of course is that the lambda has to be different for each level. There's probably a more elegant way, but the following works:
from collections import defaultdict
def deepdefaultdict(inner, depth):
return defaultdict(inner if depth == 1 else
lambda: deepdefaultdict(inner, depth - 1))
def convert(d, depth, inner):
if isinstance(d, dict):
res = deepdefaultdict(inner, depth)
res.update({x: convert(d[x], depth-1, inner) for x in d})
return res
else:
return d
Example:
import pprint
d = {1: {2: {3: 42}}}
d = convert(d, 3, int)
d[2][3][4]
pprint.pprint(d)
Output:
defaultdict(<function deepdefaultdict.<locals>.<lambda> at 0xb75ec974>,
{1: defaultdict(<function deepdefaultdict.<locals>.<lambda> at 0xb75be9bc>,
{2: defaultdict(<class 'int'>, {3: 42})}),
2: defaultdict(<function deepdefaultdict.<locals>.<lambda> at 0xb764b92c>,
{3: defaultdict(<class 'int'>, {4: 0})})})

Is this code for making a nested dictionary correct?

I had some problems with the code that was given in an answer at this post:
Can I use a nested for loop for an if-else statement with multiple conditions in python?
import pprint
board = {
'1a': 'bking',
'4e': 'bpawn',
'2c': 'bpawn',
'3f': 'bpawn',
'5h': 'bbishop',
'6d': 'wking',
'7f': 'wrook',
'2b': 'wqueen'
}
count = {}
for k, v in board.items():
count[k[0]][k[1:]] = v
pprint.pprint(count)
I wanted to get the following dictionary:
count = {'b': {'king': 1, 'pawn': 3, 'bishop': 1},
'w': {'king': 1, 'rook': 1, 'queen': 1}}
Received error:
Traceback (most recent call last):
File "/Users/Andrea_5K/Library/Mobile Documents/com~apple~CloudDocs/automateStuff2/ch5/flatToNest2.py", line 21, in <module>
count[k[0]][k[1:]] = v
KeyError: '1'
OP comment says output should be the count of each piece. That can be done as follows using setdefault
nested = {}
for k, v in board.items():
nested.setdefault(v[0], {}) # default dictionary for missing key
nested[v[0]][v[1:]] = nested[v[0]].get(v[1:], 0) + 1 # increment piece count
pprint.pprint(nested)
# Result
{'b': {'bishop': 1, 'king': 1, 'pawn': 3},
'w': {'king': 1, 'queen': 1, 'rook': 1}}
The problem in your code is that when you access nested[k[0]], you expect nested to already have this key, and you expect the corresponding value to be a dict.
The easiest way to solve this problem is to use a defaultdict(dict) that will create it on the fly when needed:
from collections import defaultdict
board = {
'1a': 'bking',
'4e': 'bpawn',
'2c': 'bpawn',
'3f': 'bpawn',
'5h': 'bbishop',
'6d': 'wking',
'7f': 'wrook',
'2b': 'wqueen'
}
nested = defaultdict(dict)
for k, v in board.items():
nested[k[0]][k[1:]] = v
print(nested)
# defaultdict(<class 'dict'>, {'1': {'a': 'bking'}, '4': {'e': 'bpawn'}, '2': {'c': 'bpawn', 'b': 'wqueen'}, '3': {'f': 'bpawn'}, '5': {'h': 'bbishop'}, '6': {'d': 'wking'}, '7': {'f': 'wrook'}})
If all you need is counts, use collections.Counter, and split the result using a collections.defaultdict afterward:
counts = defaultdict(dict)
for piece, count in Counter(board.values()).items():
counts[piece[0]][piece[1:]] = count

How to return nested dictionary from function

Is it possible to make a function that will return a nested dict depending on the arguments?
def foo(key):
d = {'a': 1, 'b': 2, 'c': {'d': 3, 'e': 4}, }
return d[key]
foo(['c']['d'])
I waiting for:
3
I'm getting:
TypeError: list indices must be integers or slices, not str
I understanding that it possible to return a whole dict, or hard code it to return a particular part of dict, like
if 'c' and 'd' in kwargs:
return d['c']['d']
elif 'c' and 'e' in kwargs:
return d['c']['e']
but it will be very inflexible
When you give ['c']['d'], you slice the list ['c'] using the letter d, which isin't possible. So what you can do is, correct the slicing:
foo('c')['d']
Or you could alter your function to slice it:
def foo(*args):
d = {'a': 1, 'b': 2, 'c': {'d': 3, 'e': 4}, }
d_old = dict(d) # if case you have to store the dict for other operations in the fucntion
for i in args:
d = d[i]
return d
>>> foo('c','d')
3
d = {'a': 1, 'b': 2, 'c': {'d': 3, 'e': 4}, }
def funt(keys):
val = d
for key in keys:
if val:
val = val.get(key)
return val
funt(['c', 'd'])
Additionally to handle key not present state.
One possible solution would be to iterate over multiple keys -
def foo(keys, d=None):
if d is None:
d = {'a': 1, 'b': 2, 'c': {'d': 3, 'e': 4}, }
if len(keys) == 1:
return d[keys[0]]
return foo(keys[1:], d[keys[0]])
foo(['c', 'd'])

Rearranging levels of a nested dictionary in python

Is there a library that would help me achieve the task to rearrange the levels of a nested dictionary
Eg: From this:
{1:{"A":"i","B":"ii","C":"i"},2:{"B":"i","C":"ii"},3:{"A":"iii"}}
To this:
{"A":{1:"i",3:"iii"},"B":{1:"ii",2:"i"},"C":{1:"i",2:"ii"}}
ie first two levels on a 3 levelled dictionary swapped. So instead of 1 mapping to A and 3 mapping to A, we have A mapping to 1 and 3.
The solution should be practical for an arbitrary depth and move from one level to any other within.
>>> d = {1:{"A":"i","B":"ii","C":"i"},2:{"B":"i","C":"ii"},3:{"A":"iii"}}
>>> keys = ['A','B','C']
>>> e = {key:{k:d[k][key] for k in d if key in d[k]} for key in keys}
>>> e
{'C': {1: 'i', 2: 'ii'}, 'B': {1: 'ii', 2: 'i'}, 'A': {1: 'i', 3: 'iii'}}
thank god for dict comprehension
One way to think about this would be to consider your data as a (named) array and to take the transpose. An easy way to achieve this would be to use the data analysis package Pandas:
import pandas as pd
df = pd.DataFrame({1: {"A":"i","B":"ii","C":"i"},
2: {"B":"i","C":"ii"},
3: {"A":"iii"}})
df.transpose().to_dict()
{'A': {1: 'i', 2: nan, 3: 'iii'},
'B': {1: 'ii', 2: 'i', 3: nan},
'C': {1: 'i', 2: 'ii', 3: nan}}
I don't really care about performance for my application of this so I haven't bothered checking how efficient this is. Its based on bubblesort so my guess is ~O(N^2).
Maybe this is convoluted, but essentially below works by:
- providing dict_swap_index a nested dictionary and a list. the list should be of the format [i,j,k]. The length should be the depth of the dictionary. Each element corresponds to which position you'd like to move each element to. e.g. [2,0,1] would indicate move element 0 to position 2, element 1 to position 0 and element 2 to position 1.
- this function performs a bubble sort on the order list and dict_, calling deep_swap to swap the levels of the dictionary which are being swapped in the order list
- deep_swap recursively calls itself to find the level provided and returns a dictionary which has been re-ordered
- swap_two_level_dict is called to swap any two levels in a dictionary.
Essentially the idea is to perform a bubble sort on the dictionary, but instead of swapping elements in a list swap levels in a dictionary.
from collections import defaultdict
def dict_swap_index(dict_, order):
for pas_no in range(len(order)-1,0,-1):
for i in range(pas_no):
if order[i] > order[i+1]:
temp = order[i]
order[i] = order[i+1]
order[i+1] = temp
dict_ = deep_swap(dict_, i)
return dict_, order
def deep_swap(dict_, level):
dict_ = deepcopy(dict_)
if level==0:
dict_ = swap_two_level_dict(dict_)
else:
for key in dict_:
dict_[key] = deep_swap(dict_[key], level-1)
return dict_
def swap_two_level_dict(a):
b = defaultdict(dict)
for key1, value1 in a.items():
for key2, value2 in value1.items():
b[key2].update({key1: value2})
return b
e.g.
test_dict = {'a': {'c': {'e':0, 'f':1}, 'd': {'e':2,'f':3}}, 'b': {'c': {'g':4,'h':5}, 'd': {'j':6,'k':7}}}
result = dict_swap_index(test_dict, [2,0,1])
result
(defaultdict(dict,
{'c': defaultdict(dict,
{'e': {'a': 0},
'f': {'a': 1},
'g': {'b': 4},
'h': {'b': 5}}),
'd': defaultdict(dict,
{'e': {'a': 2},
'f': {'a': 3},
'j': {'b': 6},
'k': {'b': 7}})}),
[0, 1, 2])

Categories