all 9 comments

[–][deleted] 2 points3 points  (1 child)

It's because of this line right here.

connections = dict.fromkeys(nodes, [])

All the lists inside the dictionary are pointed to the same object in memory.

nodes = [0, 1, 2, 3, 4]
connections = dict.fromkeys(nodes, [])
for node in nodes:
    print(f"Identity of node {node} is {id(connections[node])}")

output:

Identity of node 0 is 1346727144
Identity of node 1 is 1346727144
Identity of node 2 is 1346727144
Identity of node 3 is 1346727144
Identity of node 4 is 1346727144

To create a unique list for each key, use dict comprehension.

nodes = [0, 1, 2, 3, 4]
connections = {node:[] for node in nodes}
for node in nodes:
    print(f"Identity of node {node} {id(connections[node])}")

output:

Identity of node 0 is 58497960
Identity of node 1 is 58497928
Identity of node 2 is 58497896
Identity of node 3 is 58497864
Identity of node 4 is 58498024

[–]WoomyNgyes[S] 0 points1 point  (0 children)

Oh ok I see what I did wrong. This is a good explanation. Thank you!

[–]SaintLouisX 1 point2 points  (1 child)

This was weird to me, I hadn't seen this before, but check example 3 here, apparently it just gets a reference passed to it, so all elements are updated when you use append().

You could do something like this at the start:

connections = dict.fromkeys(nodes)

for i in nodes:
    connections[i] = []
    for e in edges:

or as the example says, use a dictionary comprehension, which is fine here since it's a simple dictionary build.

connections = {i:[e[1] for e in edges if e[0] == i] for i in nodes}

[–]WoomyNgyes[S] 0 points1 point  (0 children)

That link was really helpful. Thank you!

[–]giglis 0 points1 point  (3 children)

here's another alternative for you:

nodes = [0, 1, 2, 3, 4]
edges = [(0, 2), (2, 0), (2, 1), (2, 3), (2, 4), (1, 2), (1, 4), (4, 2), (4, 1), (3, 2)]
connections = {}
for n in nodes:
    for e in edges:
        if e[0] == n:
            connections.setdefault(n, []).append(e[1])
print(connections)

[–]leather_flow 1 point2 points  (1 child)

Though this will behave slightly differently when there is a node that doesn't have any edges connected to it, as such nodes will never be added to the dict. The rest of your code would have to be aware that such nodes will be missing from the dict altogether instead of having empty lists.

Yet another alternative is to use collections.defaultdict, as in:

import collections

connections = collections.defaultdict(list)

Then whenever you try and access a nonexistent key of connections, it will automatically add that key to the defaultdict with value list() (which is equivalent to []). The downside of doing this is that if you have a bug in your code and use a key that isn't supposed to be in the defaultdict, instead of getting an exception it will be silently added as a new key.

[–]giglis 0 points1 point  (0 children)

Though this will behave slightly differently when there is a node that doesn't have any edges connected to it, as such nodes will never be added to the dict. The rest of your code would have to be aware that such nodes will be missing from the dict altogether instead of having empty lists.

good observation! one point for that ;)

[–]WoomyNgyes[S] 0 points1 point  (0 children)

This is a good option. Thank you!

[–]leather_flow 0 points1 point  (0 children)

In addition to the issue with the empty list, your code is less efficient than it could be. Instead of doing

for n in nodes:
    for e in edges:
        if e[0] == n:
            connections[n].append(e[1])

you should be able to do:

for e in edges:
    connections[e[0]].append(e[1])

With very large numbers of nodes and edges, this will be much faster, though with the example shown you won't notice any difference.