0

I try to create a dictionary which will have a relations to different sides taken from the list.

import collections
def create_dict(sides = ["A", "B", "C"]):
    my_dict = dict.fromkeys(sides, {})
    print("my_dict created here: ", my_dict)
    for k in sides:
        remining_list = collections.deque(sides)
        remining_list.remove(k)
        my_dict[k]["relation"] = dict.fromkeys(remining_list, 0)
    return my_dict

The dict is created with empty dictionaries: ('my_dict created here: ', {'A': {}, 'C': {}, 'B': {}}) I expect an output as:

{'A': {'relation': {'B': 0, 'C': 0}},
 'B': {'relation': {'A': 0, 'C': 0}},
 'C': {'relation': {'A': 0, 'B': 0}}}

but instead each value of inner dictionary comes to the last processed dictionary like this:

{'A': {'relation': {'A': 0, 'B': 0}},
 'C': {'relation': {'A': 0, 'B': 0}},
 'B': {'relation': {'A': 0, 'B': 0}}}

Can not figure out in which step I do wrong. When I loop over the keys I give it as a value only to this particular key not to all as in the output. Also is there any more efficient way to create this dictionary?

for completes this is example of call:

if __name__=="__main__":
    my_dict = create_dict()
    print(my_dict)
Tos
  • 67
  • 1
  • 6
  • 1
    I believe this `my_dict = dict.fromkeys(sides, {})` make all keys point to the same dictionary – Dani Mesejo Nov 14 '19 at 18:50
  • 1
    @DanielMesejo Yes, that's the answer. – chepner Nov 14 '19 at 18:51
  • Duplicate (in spirit) of https://stackoverflow.com/q/1132941/1126841; here, the mutable default is the value provided by `fromkeys`, rather than a hard-coded default parameter value. – chepner Nov 14 '19 at 18:53
  • Why are you using a deque? Deques allow efficient mutation at the *ends*, but `remove` is still O(n) (because it might have to remove an item from the interior of the deque). There's no benefit over a list here. – chepner Nov 14 '19 at 18:58

2 Answers2

3

Check the doc on dict.fromkeys. All the keys in the returned dict share the same value. You need a constructor that uses a factory, instead of a value.

Try my_dict = {i:{} for i in sides}.

For the complete solution, I'd use a nested version.

def create_dict(sides):
    return {i:dict(relation={j:0 for j in sides if j!=i}) 
               for i in sides}

Note that if order matters, the above only works on CPython 3.6 or later, or any conformant python 3.7 or later. You could use OrderedDict in earlier versions, but it has performance penalties.

Also note that it is generally considered bad form to use lists or other mutable types in default arguments, so you probably want

def create_dict(sides=("A", "B", "C")):
    ...

instead.

Perkins
  • 2,409
  • 25
  • 23
0

Since you are using collections already, I suggest you use defaultdict:

import collections


def create_dict(sides=["A", "B", "C"]):
    my_dict = collections.defaultdict(dict)
    for k in sides:
        remining_list = collections.deque(sides)
        remining_list.remove(k)
        my_dict[k]["relation"] = dict.fromkeys(remining_list, 0)
    return dict(my_dict.items())


print(create_dict())

Output

{'A': {'relation': {'B': 0, 'C': 0}}, 'B': {'relation': {'A': 0, 'C': 0}}, 'C': {'relation': {'A': 0, 'B': 0}}}
Dani Mesejo
  • 61,499
  • 6
  • 49
  • 76
  • Don't do `defaultdict(lambda: {})` just do `defaultdict(dict)`, base types are 0-argument callables. – Perkins Nov 14 '19 at 18:58
  • @Perkins Nice catch! Updated the answer. – Dani Mesejo Nov 14 '19 at 18:58
  • Also, note that there's some overhead in using defaultdict, if all the keys are known ahead of time, it's often better to preallocate them. If I were going to go the defaultdict route, I'd probably make the nested dictionary default to its complete structure `defaultdict(lambda: dict(relation=defaultdict(int)))` But that's pretty messy. – Perkins Nov 14 '19 at 19:03
  • There's no reason to use `collections` *other* than for `defaultdict`, but it's still a nice idea :) – chepner Nov 14 '19 at 19:03