Python nested loop where the second loop is always one object ahead of the first loop
Question:
I am trying to optimize a node data structure of a graph/network by avoiding duplicate edges. The graph is undirected and has no loops (in the sense that no node has edges to itself). Each node data structure contains a list of edges. To this end, I have the following function for adding an edge between two nodes of the graph:
"""
Creates an edge between `node1` and `node2`.
If i < j and there is an edge between node i and node j, then
the edge between node i and node j will be stored in node i.
"""
def create_edge(self, node1, node2):
if node1.id < node2.id:
node1.edges.append(node2.id)
else:
node2.edges.append(node1.id)
And then, as part of the instantiation of the graph/network object, the following code is executed to create edges between nodes, based on some probability:
for node1 in self.green_network:
for node2 in range(node1 + 1, self.green_network):
if random.random() < self.probability_of_an_edge:
node1.create_edge(node2)
Now, I realise this second segment of code is incorrect, but I think it roughly illustrates what I am trying to do. Given my stated goal, it seems to me that, for every node in the first loop, for the second loop, I want to loop from (every node + 1) to the last node. So, for instance, we first loop between node.id = 1 to node.id = 2, node.id = 3, node.id = 4, …, and then between node.id = 2 to node.id = 3, node.id = 4, node.id = 5, …, and then between node.id = 3 to node.id = 4, node.id = 5, node.id = 6, …, and so on. That way, we avoid duplicates, given how the create_edge
function behaves.
This is related, but it doesn’t seem to answer the question of iterating over objects, which is what I’m trying to do here.
Answers:
As suggested in the comments, you could/should generate all the combos and consider them with equal probability.
from itertools import combinations
from random import random
class Node:
count = 0
def __init__(self):
self.edges = []
self.idx = Node.count
Node.count += 1
def add_edge(self, other: "Node"):
# this adds an edge to BOTH nodes
# if you *really* want, you could compare indices here and only add one edge... Buyer be ware! :)
self.edges.append(other.idx)
other.edges.append(self.idx)
# make 5 nodes
nodes = [Node() for _ in range(5)]
# this will produce a GENERATOR for all 10 possible combinations (5 choose 2 = 10)
all_pairs = combinations(nodes,2)
for n1, n2 in all_pairs:
if random() > 0.25: # 75% chance of addition...
n1.add_edge(n2)
# check
for node in nodes:
print(f'Node {node.idx} connects to: {node.edges}')
Output:
Node 0 connects to: [3]
Node 1 connects to: [2, 4]
Node 2 connects to: [1]
Node 3 connects to: [0, 4]
Node 4 connects to: [1, 3]
I am trying to optimize a node data structure of a graph/network by avoiding duplicate edges. The graph is undirected and has no loops (in the sense that no node has edges to itself). Each node data structure contains a list of edges. To this end, I have the following function for adding an edge between two nodes of the graph:
"""
Creates an edge between `node1` and `node2`.
If i < j and there is an edge between node i and node j, then
the edge between node i and node j will be stored in node i.
"""
def create_edge(self, node1, node2):
if node1.id < node2.id:
node1.edges.append(node2.id)
else:
node2.edges.append(node1.id)
And then, as part of the instantiation of the graph/network object, the following code is executed to create edges between nodes, based on some probability:
for node1 in self.green_network:
for node2 in range(node1 + 1, self.green_network):
if random.random() < self.probability_of_an_edge:
node1.create_edge(node2)
Now, I realise this second segment of code is incorrect, but I think it roughly illustrates what I am trying to do. Given my stated goal, it seems to me that, for every node in the first loop, for the second loop, I want to loop from (every node + 1) to the last node. So, for instance, we first loop between node.id = 1 to node.id = 2, node.id = 3, node.id = 4, …, and then between node.id = 2 to node.id = 3, node.id = 4, node.id = 5, …, and then between node.id = 3 to node.id = 4, node.id = 5, node.id = 6, …, and so on. That way, we avoid duplicates, given how the create_edge
function behaves.
This is related, but it doesn’t seem to answer the question of iterating over objects, which is what I’m trying to do here.
As suggested in the comments, you could/should generate all the combos and consider them with equal probability.
from itertools import combinations
from random import random
class Node:
count = 0
def __init__(self):
self.edges = []
self.idx = Node.count
Node.count += 1
def add_edge(self, other: "Node"):
# this adds an edge to BOTH nodes
# if you *really* want, you could compare indices here and only add one edge... Buyer be ware! :)
self.edges.append(other.idx)
other.edges.append(self.idx)
# make 5 nodes
nodes = [Node() for _ in range(5)]
# this will produce a GENERATOR for all 10 possible combinations (5 choose 2 = 10)
all_pairs = combinations(nodes,2)
for n1, n2 in all_pairs:
if random() > 0.25: # 75% chance of addition...
n1.add_edge(n2)
# check
for node in nodes:
print(f'Node {node.idx} connects to: {node.edges}')
Output:
Node 0 connects to: [3]
Node 1 connects to: [2, 4]
Node 2 connects to: [1]
Node 3 connects to: [0, 4]
Node 4 connects to: [1, 3]