diff --git a/USAGE.TXT b/USAGE.TXT index 07a07aa..bc1e85a 100644 --- a/USAGE.TXT +++ b/USAGE.TXT @@ -29,3 +29,38 @@ nd4["human"]["chr1"]+=3 nd4["human"]["chr3"]+=4 +3) Dictionaries with lists or sets (or arbitrary objects) can have custom combine_policies: + + + > nd1 = nested_dict({'a':1,'f':[1,3]}) + > nd2 = nested_dict({'a':1,'f':[1,2]}) + + # use built-in combine_policy to merge lists + > nd1.update(nd2, combine_policies=['uniquely_extend_list']) + {'a':1,'f':[1,3,2]} + +Here we are using the built-in combine_policy_options. +If your dict contains structures you may specify which ones update() will use with the combine_policies kwarg (list). +You may also supply your own combine_policy_options kwarg (list) + + default_combine_policy_options=[ + {'name': 'uniquely_extend_list', 'signature': (list,list), + 'combiner': lambda x,y: x + list(set(y) - set(x)) }, + + {'name': 'list_of_union', 'signature': (list,list), + 'combiner': lambda x,y: list(set(y) + set(x)) }, + + {'name': 'symmetric_difference', 'signature': (set,set), + 'combiner': lambda x,y: x.symmetric_difference(y)}, + ] + +Here is an example of creating your own useful combiner + + > combine_policy_options=[ + {'name': 'rabbits', 'signature': (list,list), + 'combiner': lambda x,y: 'look rabbits' }] + > nd1 = nested_dict.nested_dict({'a':1,'f':[1,3,3]}) + > nd2 = nested_dict.nested_dict({'a':1,'f':[1,2]}) + > nd1.update(nd2, + combine_policies=['rabbits'], combine_plicy_options=combine_policy_options) + {'a':1,'f':'look rabbits'}) \ No newline at end of file diff --git a/nested_dict/implementation.py b/nested_dict/implementation.py index 497bd84..86fad1b 100644 --- a/nested_dict/implementation.py +++ b/nested_dict/implementation.py @@ -149,26 +149,47 @@ def nested_dict_from_dict(orig_dict, nd): nd[key] = value return nd +default_combine_policy_options=[ + {'name': 'uniquely_extend_list', 'signature': (list,list), + 'combiner': lambda x,y: x + [yy for yy in y if yy not in x] }, + {'name': 'sorted_list_of_union', 'signature': (list,list), + 'combiner': lambda x,y: sorted(list(set(y).union(set(x)))) }, + {'name': 'symmetric_difference', 'signature': (set,set), + 'combiner': lambda x,y: x.symmetric_difference(y)}, + ] + + +def get_combine_policy(val1, val2, combine_policies, combine_policy_options): + for option in combine_policy_options: + if ((type(val1),type(val2)) == option['signature']) and (option['name'] in combine_policies): + return option['combiner'] + else: + return None -def _recursive_update(nd, other): +def _recursive_update(nd, other, combine_policies=[], combine_policy_options=[]): for key, value in iteritems(other): #print ("key=", key) + combine_policy = get_combine_policy(nd[key], other[key], combine_policies, combine_policy_options) + #print("combine_policy for {} {} is {}".format(key, value, combine_policy)) if isinstance(value, (dict,)): # recursive update if my item is nested_dict if isinstance(nd[key], (_recursive_dict,)): #print ("recursive update", key, type(nd[key])) - _recursive_update(nd[key], other[key]) + _recursive_update(nd[key], other[key], combine_policies) # update if my item is dict elif isinstance(nd[key], (dict,)): #print ("update", key, type(nd[key])) - nd[key].update(other[key]) - + nd[key].update(other[key], combine_policies) # overwrite else: #print ("self not nested dict or dict: overwrite", key) nd[key] = value + # combine by specified matching combine_policy + elif combine_policy: + #print(nd[key], combine_policy(nd[key], value)) + nd[key] = combine_policy(nd[key], value) # other not dict: overwrite else: #print ("other not dict: overwrite", key) @@ -188,9 +209,10 @@ class nested_dict(_recursive_dict): Uses defaultdict to automatically add levels of nested dicts and other types. """ - def update(self, other): + def update(self, other, combine_policies=[], combine_policy_options=[]): """Update recursively.""" - _recursive_update(self, other) + combine_policy_options = combine_policy_options + default_combine_policy_options + _recursive_update(self, other, combine_policies, combine_policy_options) def __init__(self, *param, **named_param): """ @@ -229,3 +251,4 @@ def __init__(self, *param, **named_param): "2) an existing dict to be converted into a nested dict " "(factory = %s. len(param) = %d, param = %s" % (self.factory, len(param), param)) + diff --git a/tests/test_nested_dict.py b/tests/test_nested_dict.py index 535f941..d6d7728 100644 --- a/tests/test_nested_dict.py +++ b/tests/test_nested_dict.py @@ -243,3 +243,35 @@ def test_update(self): # d1[2][3][4][5] = 6 but d1[2][3][5] should still be a default dict of list d1[2][3][5].append(4) self.assertEqual(d1.to_dict(), {1: {2: {3: [4], 4: [4]}}, 2: {3: {4: {5: 6}, 5: [4]}}}) + + def test_update_with_combine_policies(self): + """Test update with combine_policies""" + import nested_dict + + # + # uniquely_extend_list + # + nd1 = nested_dict.nested_dict({'a':1,'f':[1,3,3]}) + nd2 = nested_dict.nested_dict({'a':1,'f':[1,2]}) + nd1.update(nd2, combine_policies=['uniquely_extend_list']) + self.assertEqual(nd1.to_dict(), {'a':1,'f':[1,3,3,2]}) + + # + # list_of_union + # + nd1 = nested_dict.nested_dict({'a':1,'f':[1,3,3]}) + nd2 = nested_dict.nested_dict({'a':1,'f':[1,2]}) + nd1.update(nd2, combine_policies=['sorted_list_of_union']) + self.assertEqual(nd1.to_dict(), {'a':1,'f':[1,2,3]}) + + # + # custom_combine_policy + # + combine_policy_options=[ + {'name': 'rabbits', 'signature': (list,list), + 'combiner': lambda x,y: 'look rabbits' } + ] + nd1 = nested_dict.nested_dict({'a':1,'f':[1,3,3]}) + nd2 = nested_dict.nested_dict({'a':1,'f':[1,2]}) + nd1.update(nd2, combine_policies=['rabbits'], combine_policy_options=combine_policy_options) + self.assertEqual(nd1.to_dict(), {'a':1,'f':'look rabbits'}) \ No newline at end of file