Skip to content

Commit 3c072e9

Browse files
author
Toto Lin
authored
Merge pull request #45 from YanWQ-monad/master
Add DAG generator
2 parents c895c1d + e89460c commit 3c072e9

File tree

3 files changed

+271
-1
lines changed

3 files changed

+271
-1
lines changed

cyaron/graph.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,122 @@ def graph(point_count, edge_count, **kwargs):
274274
i += 1
275275
return graph
276276

277+
@staticmethod
278+
def DAG(point_count, edge_count, **kwargs):
279+
"""DAG(point_count, edge_count, **kwargs) -> Graph
280+
Factory method. Return a graph with point_count vertexes and edge_count edges.
281+
int point_count -> the count of vertexes
282+
int edge_count -> the count of edges
283+
**kwargs(Keyword args):
284+
bool self_loop = False -> whether to allow self loops or not
285+
bool repeated_edges = True -> whether to allow repeated edges or not
286+
bool loop = False -> whether to allow loops or not
287+
(int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included)
288+
int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included)
289+
int/float weight_gen()
290+
= lambda: random.randint(weight_limit[0], weight_limit[1])
291+
-> the generator of the weights. It should return the weight. The default way is to use the random.randint()
292+
"""
293+
if edge_count < point_count - 1:
294+
raise Exception("the number of edges of connected graph must more than the number of nodes - 1")
295+
296+
self_loop = kwargs.get("self_loop", False) # DAG default has no loop
297+
repeated_edges = kwargs.get("repeated_edges", True)
298+
loop = kwargs.get("loop", False)
299+
weight_limit = kwargs.get("weight_limit", (1, 1))
300+
if not list_like(weight_limit):
301+
weight_limit = (1, weight_limit)
302+
weight_gen = kwargs.get(
303+
"weight_gen", lambda: random.randint(
304+
weight_limit[0], weight_limit[1]))
305+
306+
used_edges = set()
307+
edge_buf = list(Graph.tree(point_count, weight_limit=weight_gen()).iterate_edges())
308+
graph = Graph(point_count, directed=True)
309+
310+
for edge in edge_buf:
311+
if loop and random.randint(1, 2) == 1:
312+
edge.start, edge.end = edge.end, edge.start
313+
graph.add_edge(edge.start, edge.end, weight=edge.weight)
314+
315+
if not repeated_edges:
316+
used_edges.add((edge.start, edge.end))
317+
318+
i = point_count - 1
319+
while i < edge_count:
320+
u = random.randint(1, point_count)
321+
v = random.randint(1, point_count)
322+
323+
if not loop and u > v:
324+
u, v = v, u
325+
326+
if (not self_loop and u == v) or (not repeated_edges and (u, v) in used_edges):
327+
# Then we generate a new pair of nodes
328+
continue
329+
330+
graph.add_edge(u, v, weight=weight_gen())
331+
332+
if not repeated_edges:
333+
used_edges.add((u, v))
334+
335+
i += 1
336+
337+
return graph
338+
339+
@staticmethod
340+
def UDAG(point_count, edge_count, **kwargs):
341+
"""UDAG(point_count, edge_count, **kwargs) -> Graph
342+
Factory method. Return a graph with point_count vertexes and edge_count edges.
343+
int point_count -> the count of vertexes
344+
int edge_count -> the count of edges
345+
**kwargs(Keyword args):
346+
bool self_loop = True -> whether to allow self loops or not
347+
bool repeated_edges = True -> whether to allow repeated edges or not
348+
(int,int) weight_limit = (1,1) -> the limit of weight. index 0 is the min limit, and index 1 is the max limit(both included)
349+
int weight_limit -> If you use a int for this arg, it means the max limit of the weight(included)
350+
int/float weight_gen()
351+
= lambda: random.randint(weight_limit[0], weight_limit[1])
352+
-> the generator of the weights. It should return the weight. The default way is to use the random.randint()
353+
"""
354+
if edge_count < point_count - 1:
355+
raise Exception("the number of edges of connected graph must more than the number of nodes - 1")
356+
357+
self_loop = kwargs.get("self_loop", True)
358+
repeated_edges = kwargs.get("repeated_edges", True)
359+
weight_limit = kwargs.get("weight_limit", (1, 1))
360+
if not list_like(weight_limit):
361+
weight_limit = (1, weight_limit)
362+
weight_gen = kwargs.get(
363+
"weight_gen", lambda: random.randint(
364+
weight_limit[0], weight_limit[1]))
365+
366+
used_edges = set()
367+
graph = Graph.tree(point_count, weight_limit=weight_gen(), directed=False)
368+
369+
for edge in graph.iterate_edges():
370+
if not repeated_edges:
371+
used_edges.add((edge.start, edge.end))
372+
used_edges.add((edge.end, edge.start))
373+
374+
i = point_count - 1
375+
while i < edge_count:
376+
u = random.randint(1, point_count)
377+
v = random.randint(1, point_count)
378+
379+
if (not self_loop and u == v) or (not repeated_edges and (u, v) in used_edges):
380+
# Then we generate a new pair of nodes
381+
continue
382+
383+
graph.add_edge(u, v, weight=weight_gen())
384+
385+
if not repeated_edges:
386+
used_edges.add((u, v))
387+
used_edges.add((v, u))
388+
389+
i += 1
390+
391+
return graph
392+
277393
@staticmethod
278394
def hack_spfa(point_count, **kwargs):
279395
"""hack_spfa(point_count, **kwargs) -> None

cyaron/tests/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
from .io_test import TestIO
33
from .str_test import TestString
44
from .polygon_test import TestPolygon
5-
from .compare_test import TestCompare
5+
from .compare_test import TestCompare
6+
from .graph_test import TestGraph

cyaron/tests/graph_test.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import unittest
2+
from cyaron import Graph
3+
4+
5+
class UnionFindSet:
6+
def __init__(self, size):
7+
self.father = [0] + [i + 1 for i in range(size)]
8+
9+
def get_father(self, node):
10+
if self.father[node] == node:
11+
return node
12+
else:
13+
self.father[node] = self.get_father(self.father[node])
14+
return self.father[node]
15+
16+
def merge(self, l, r):
17+
l = self.get_father(l)
18+
r = self.get_father(r)
19+
self.father[l] = r
20+
21+
def test_same(self, l, r):
22+
return self.get_father(l) == self.get_father(r)
23+
24+
25+
def tarjan(graph, n):
26+
def new_array(len, val=0):
27+
return [val for _ in range(len+1)]
28+
29+
instack = new_array(n, False)
30+
low = new_array(n)
31+
dfn = new_array(n, 0)
32+
stap = new_array(n)
33+
belong = new_array(n)
34+
var = [0, 0, 0] # cnt, bc, stop
35+
# cnt = bc = stop = 0
36+
37+
def dfs(cur):
38+
var[0] += 1
39+
dfn[cur] = low[cur] = var[0]
40+
instack[cur] = True
41+
stap[var[2]] = cur
42+
var[2] += 1
43+
44+
for v in graph.edges[cur]:
45+
if dfn[v.end] == 0:
46+
dfs(v.end)
47+
low[cur] = min(low[cur], low[v.end])
48+
elif instack[v.end]:
49+
low[cur] = min(low[cur], dfn[v.end])
50+
51+
if dfn[cur] == low[cur]:
52+
v = cur + 1 # set v != cur
53+
var[1] += 1
54+
while v != cur:
55+
var[2] -= 1
56+
v = stap[var[2]]
57+
instack[v] = False
58+
belong[v] = var[1]
59+
60+
for i in range(n):
61+
if dfn[i+1] == 0:
62+
dfs(i+1)
63+
64+
return belong
65+
66+
67+
class TestGraph(unittest.TestCase):
68+
69+
def test_self_loop(self):
70+
graph_size = 20
71+
for _ in range(20):
72+
graph = Graph.graph(graph_size, int(graph_size*2), self_loop=True)
73+
has_self_loop = max([e.start == e.end for e in graph.iterate_edges()])
74+
if has_self_loop:
75+
break
76+
self.assertTrue(has_self_loop)
77+
78+
for _ in range(10):
79+
graph = Graph.graph(graph_size, int(graph_size*2), self_loop=False)
80+
self.assertFalse(max([e.start == e.end for e in graph.iterate_edges()]))
81+
82+
def test_repeated_edges(self):
83+
graph_size = 20
84+
for _ in range(20):
85+
graph = Graph.graph(graph_size, int(graph_size*2), repeated_edges=True)
86+
edges = [(e.start, e.end) for e in graph.iterate_edges()]
87+
has_repeated_edges = len(edges) > len(set(edges))
88+
if has_repeated_edges:
89+
break
90+
self.assertTrue(has_repeated_edges)
91+
92+
for _ in range(10):
93+
graph = Graph.graph(graph_size, int(graph_size*2), repeated_edges=False)
94+
edges = list(graph.iterate_edges())
95+
self.assertEqual(len(edges), len(set(edges)))
96+
97+
def test_tree_connected(self):
98+
graph_size = 20
99+
for _ in range(20):
100+
ufs = UnionFindSet(graph_size)
101+
tree = Graph.tree(graph_size)
102+
for edge in tree.iterate_edges():
103+
ufs.merge(edge.start, edge.end)
104+
for i in range(graph_size-1):
105+
self.assertTrue(ufs.test_same(i+1, i+2))
106+
107+
108+
def test_DAG(self):
109+
graph_size = 20
110+
for _ in range(10): # test 10 times
111+
ufs = UnionFindSet(graph_size)
112+
graph = Graph.DAG(graph_size, int(graph_size*1.6), repeated_edges=False, self_loop=False, loop=True)
113+
114+
self.assertEqual(len(list(graph.iterate_edges())), int(graph_size*1.6))
115+
116+
for edge in graph.iterate_edges():
117+
ufs.merge(edge.start, edge.end)
118+
for i in range(graph_size-1):
119+
self.assertTrue(ufs.test_same(i+1, i+2))
120+
121+
def test_DAG_without_loop(self):
122+
graph_size = 20
123+
for _ in range(10): # test 10 times
124+
ufs = UnionFindSet(graph_size)
125+
graph = Graph.DAG(graph_size, int(graph_size*1.6), repeated_edges=False, self_loop=False, loop=False)
126+
127+
self.assertEqual(len(list(graph.iterate_edges())), int(graph_size*1.6))
128+
129+
for edge in graph.iterate_edges():
130+
ufs.merge(edge.start, edge.end)
131+
for i in range(graph_size-1):
132+
self.assertTrue(ufs.test_same(i+1, i+2))
133+
134+
belong = tarjan(graph, graph_size)
135+
self.assertEqual(max(belong), graph_size)
136+
137+
def test_undirected_graph(self):
138+
graph_size = 20
139+
for _ in range(10): # test 10 times
140+
ufs = UnionFindSet(graph_size)
141+
graph = Graph.UDAG(graph_size, int(graph_size*1.6), repeated_edges=False, self_loop=False)
142+
143+
self.assertEqual(len(list(graph.iterate_edges())), int(graph_size*1.6))
144+
145+
for edge in graph.iterate_edges():
146+
ufs.merge(edge.start, edge.end)
147+
for i in range(graph_size-1):
148+
self.assertTrue(ufs.test_same(i+1, i+2))
149+
150+
def test_DAG_boundary(self):
151+
with self.assertRaises(Exception, msg="the number of edges of connected graph must more than the number of nodes - 1"):
152+
Graph.DAG(8, 6)
153+
Graph.DAG(8, 7)

0 commit comments

Comments
 (0)