Skip to content

Commit 476534c

Browse files
Merge pull request #134 from weilycoder/dev4
2 parents aa1b6e7 + 011d6cc commit 476534c

File tree

3 files changed

+133
-43
lines changed

3 files changed

+133
-43
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ docs/_build/
132132
target/
133133

134134
# Pycharm
135-
venv
135+
venv/
136136

137137
*.DS_Store
138138

cyaron/graph.py

Lines changed: 111 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
from .utils import *
22
import random
3+
from typing import TypeVar, Callable
4+
5+
6+
__all__ = ["Edge", "Graph"]
37

48

59
class Edge:
610
"""Class Edge: A class of the edge in the graph"""
11+
712
def __init__(self, u, v, w):
813
"""__init__(self, u, v, w) -> None
914
Initialize a edge.
@@ -26,11 +31,13 @@ def unweighted_edge(edge):
2631
"""unweighted_edge(edge) -> str
2732
Return a string to output the edge without weight. The string contains the start vertex, end vertex(u,v) and splits with space.
2833
"""
29-
return '%d %d'%(edge.start,edge.end)
34+
return '%d %d' % (edge.start, edge.end)
35+
3036

3137
class Graph:
3238
"""Class Graph: A class of the graph
3339
"""
40+
3441
def __init__(self, point_count, directed=False):
3542
"""__init__(self, point_count) -> None
3643
Initialize a graph.
@@ -49,6 +56,17 @@ def edge_count(self):
4956
cnt //= 2
5057
return cnt
5158

59+
def to_matrix(self, **kwargs):
60+
"""to_matrix(self, **kwargs) -> GraphMatrix
61+
Convert the graph to adjacency matrix.
62+
**kwargs(Keyword args):
63+
int default = -1 -> the default value when the edge does not exist.
64+
Any merge(Any, Edge)
65+
= lambda val, edge: edge.weight
66+
-> the mapping from the old values in matrix and the edges to the new values in matrix.
67+
"""
68+
return GraphMatrix(self, **kwargs)
69+
5270
def to_str(self, **kwargs):
5371
"""to_str(self, **kwargs) -> str
5472
Convert the graph to string with format. Splits with "\n"
@@ -66,7 +84,8 @@ def to_str(self, **kwargs):
6684
edge_buf = []
6785
for edge in self.iterate_edges():
6886
edge_buf.append(
69-
Edge(new_node_id[edge.start], new_node_id[edge.end], edge.weight))
87+
Edge(new_node_id[edge.start], new_node_id[edge.end],
88+
edge.weight))
7089
random.shuffle(edge_buf)
7190
for edge in edge_buf:
7291
if not self.directed and random.randint(0, 1) == 0:
@@ -164,9 +183,10 @@ def tree(point_count, chain=0, flower=0, **kwargs):
164183
if not list_like(weight_limit):
165184
weight_limit = (1, weight_limit)
166185
weight_gen = kwargs.get(
167-
"weight_gen", lambda: random.randint(
168-
weight_limit[0], weight_limit[1]))
169-
father_gen = kwargs.get("father_gen", lambda cur: random.randrange(1, cur))
186+
"weight_gen",
187+
lambda: random.randint(weight_limit[0], weight_limit[1]))
188+
father_gen = kwargs.get("father_gen",
189+
lambda cur: random.randrange(1, cur))
170190

171191
if not 0 <= chain <= 1 or not 0 <= flower <= 1:
172192
raise Exception("chain and flower must be between 0 and 1")
@@ -213,33 +233,35 @@ def binary_tree(point_count, left=0, right=0, **kwargs):
213233
if not list_like(weight_limit):
214234
weight_limit = (1, weight_limit)
215235
weight_gen = kwargs.get(
216-
"weight_gen", lambda: random.randint(
217-
weight_limit[0], weight_limit[1]))
236+
"weight_gen",
237+
lambda: random.randint(weight_limit[0], weight_limit[1]))
218238

219239
if not 0 <= left <= 1 or not 0 <= right <= 1:
220240
raise Exception("left and right must be between 0 and 1")
221241
if left + right > 1:
222242
raise Exception("left plus right must be smaller than 1")
223-
224-
can_left=[1]
225-
can_right=[1]
243+
244+
can_left = [1]
245+
can_right = [1]
226246
graph = Graph(point_count, directed)
227247
for i in range(2, point_count + 1):
228248
edge_pos = random.random()
229249
node = 0
230250
# Left
231-
if edge_pos < left or left + right < edge_pos <= (1.0 - left - right) / 2:
232-
point_index = random.randint(0,len(can_left)-1)
251+
if edge_pos < left or left + right < edge_pos <= (1.0 - left -
252+
right) / 2:
253+
point_index = random.randint(0, len(can_left) - 1)
233254
node = can_left[point_index]
234-
del_last_node = can_left.pop() # Save a copy of the last element
255+
del_last_node = can_left.pop(
256+
) # Save a copy of the last element
235257
if point_index < len(can_left):
236258
# If the chosen element isn't the last one,
237259
# Copy the last one to the position of the chosen one
238260
can_left[point_index] = del_last_node
239261
# Right
240262
else:
241-
# elif left <= edge_pos <= left + right or (1.0 - left - right) / 2 < edge_pos < 1:
242-
point_index = random.randint(0,len(can_right)-1)
263+
# elif left <= edge_pos <= left + right or (1.0 - left - right) / 2 < edge_pos < 1:
264+
point_index = random.randint(0, len(can_right) - 1)
243265
node = can_right[point_index]
244266
del_last_node = can_right.pop()
245267
if point_index < len(can_right):
@@ -278,16 +300,17 @@ def graph(point_count, edge_count, **kwargs):
278300
if not list_like(weight_limit):
279301
weight_limit = (1, weight_limit)
280302
weight_gen = kwargs.get(
281-
"weight_gen", lambda: random.randint(
282-
weight_limit[0], weight_limit[1]))
303+
"weight_gen",
304+
lambda: random.randint(weight_limit[0], weight_limit[1]))
283305
graph = Graph(point_count, directed)
284306
used_edges = set()
285307
i = 0
286308
while i < edge_count:
287309
u = random.randint(1, point_count)
288310
v = random.randint(1, point_count)
289311

290-
if (not self_loop and u == v) or (not repeated_edges and (u, v) in used_edges):
312+
if (not self_loop and u == v) or (not repeated_edges and
313+
(u, v) in used_edges):
291314
# Then we generate a new pair of nodes
292315
continue
293316

@@ -318,9 +341,11 @@ def DAG(point_count, edge_count, **kwargs):
318341
-> the generator of the weights. It should return the weight. The default way is to use the random.randint()
319342
"""
320343
if edge_count < point_count - 1:
321-
raise Exception("the number of edges of connected graph must more than the number of nodes - 1")
344+
raise Exception(
345+
"the number of edges of connected graph must more than the number of nodes - 1"
346+
)
322347

323-
self_loop = kwargs.get("self_loop", False) # DAG default has no loop
348+
self_loop = kwargs.get("self_loop", False) # DAG default has no loop
324349
repeated_edges = kwargs.get("repeated_edges", True)
325350
loop = kwargs.get("loop", False)
326351
if not repeated_edges:
@@ -332,21 +357,22 @@ def DAG(point_count, edge_count, **kwargs):
332357
if not list_like(weight_limit):
333358
weight_limit = (1, weight_limit)
334359
weight_gen = kwargs.get(
335-
"weight_gen", lambda: random.randint(
336-
weight_limit[0], weight_limit[1]))
337-
360+
"weight_gen",
361+
lambda: random.randint(weight_limit[0], weight_limit[1]))
362+
338363
used_edges = set()
339-
edge_buf = list(Graph.tree(point_count, weight_gen=weight_gen).iterate_edges())
364+
edge_buf = list(
365+
Graph.tree(point_count, weight_gen=weight_gen).iterate_edges())
340366
graph = Graph(point_count, directed=True)
341367

342368
for edge in edge_buf:
343369
if loop and random.randint(1, 2) == 1:
344370
edge.start, edge.end = edge.end, edge.start
345371
graph.add_edge(edge.start, edge.end, weight=edge.weight)
346-
372+
347373
if not repeated_edges:
348374
used_edges.add((edge.start, edge.end))
349-
375+
350376
i = point_count - 1
351377
while i < edge_count:
352378
u = random.randint(1, point_count)
@@ -355,7 +381,8 @@ def DAG(point_count, edge_count, **kwargs):
355381
if not loop and u > v:
356382
u, v = v, u
357383

358-
if (not self_loop and u == v) or (not repeated_edges and (u, v) in used_edges):
384+
if (not self_loop and u == v) or (not repeated_edges and
385+
(u, v) in used_edges):
359386
# Then we generate a new pair of nodes
360387
continue
361388

@@ -383,8 +410,10 @@ def UDAG(point_count, edge_count, **kwargs):
383410
= lambda: random.randint(weight_limit[0], weight_limit[1])
384411
-> the generator of the weights. It should return the weight. The default way is to use the random.randint()
385412
"""
386-
if edge_count < point_count - 1:
387-
raise Exception("the number of edges of connected graph must more than the number of nodes - 1")
413+
if edge_count < point_count - 1:
414+
raise Exception(
415+
"the number of edges of connected graph must more than the number of nodes - 1"
416+
)
388417

389418
self_loop = kwargs.get("self_loop", True)
390419
repeated_edges = kwargs.get("repeated_edges", True)
@@ -397,23 +426,24 @@ def UDAG(point_count, edge_count, **kwargs):
397426
if not list_like(weight_limit):
398427
weight_limit = (1, weight_limit)
399428
weight_gen = kwargs.get(
400-
"weight_gen", lambda: random.randint(
401-
weight_limit[0], weight_limit[1]))
402-
429+
"weight_gen",
430+
lambda: random.randint(weight_limit[0], weight_limit[1]))
431+
403432
used_edges = set()
404433
graph = Graph.tree(point_count, weight_gen=weight_gen, directed=False)
405434

406435
for edge in graph.iterate_edges():
407436
if not repeated_edges:
408437
used_edges.add((edge.start, edge.end))
409438
used_edges.add((edge.end, edge.start))
410-
439+
411440
i = point_count - 1
412441
while i < edge_count:
413442
u = random.randint(1, point_count)
414443
v = random.randint(1, point_count)
415444

416-
if (not self_loop and u == v) or (not repeated_edges and (u, v) in used_edges):
445+
if (not self_loop and u == v) or (not repeated_edges and
446+
(u, v) in used_edges):
417447
# Then we generate a new pair of nodes
418448
continue
419449

@@ -459,8 +489,8 @@ def hack_spfa(point_count, **kwargs):
459489
if not list_like(weight_limit):
460490
weight_limit = (1, weight_limit)
461491
weight_gen = kwargs.get(
462-
"weight_gen", lambda: random.randint(
463-
weight_limit[0], weight_limit[1]))
492+
"weight_gen",
493+
lambda: random.randint(weight_limit[0], weight_limit[1]))
464494

465495
point_to_skip = point_count + 3
466496
graph = Graph(point_count, directed)
@@ -470,15 +500,18 @@ def hack_spfa(point_count, **kwargs):
470500

471501
for i in range(1, half):
472502
(x, y) = (i, i + 1)
473-
graph.add_edge(x + (x >= point_to_skip), y +
474-
(y >= point_to_skip), weight=weight_gen())
503+
graph.add_edge(x + (x >= point_to_skip),
504+
y + (y >= point_to_skip),
505+
weight=weight_gen())
475506
(x, y) = (i + half, i + half + 1)
476-
graph.add_edge(x + (x >= point_to_skip), y +
477-
(y >= point_to_skip), weight=weight_gen())
507+
graph.add_edge(x + (x >= point_to_skip),
508+
y + (y >= point_to_skip),
509+
weight=weight_gen())
478510
for i in range(1, half + 1):
479511
(x, y) = (i, i + half)
480-
graph.add_edge(x + (x >= point_to_skip), y +
481-
(y >= point_to_skip), weight=weight_gen())
512+
graph.add_edge(x + (x >= point_to_skip),
513+
y + (y >= point_to_skip),
514+
weight=weight_gen())
482515

483516
for i in range(extraedg):
484517
u = random.randint(1, point_count)
@@ -495,3 +528,39 @@ def _calc_max_edge(point_count, directed, self_loop):
495528
if self_loop:
496529
max_edge += point_count
497530
return max_edge
531+
532+
533+
class GraphMatrix:
534+
"""
535+
Class GraphMatrix: A class of the graph represented by adjacency matrix.
536+
537+
*Deprecation warning: This class may be removed after a generic matrix class is implemented in the project.*
538+
"""
539+
540+
T = TypeVar('T')
541+
542+
def __init__(self,
543+
graph: Graph,
544+
default: T = -1,
545+
merge: Callable[[T, Edge],
546+
T] = lambda val, edge: edge.weight):
547+
"""
548+
Args:
549+
graph: the graph to convert,
550+
default: the default value when the edge does not exist,
551+
merge: the mapping from the old values in matrix and the edges to the new values in matrix.
552+
"""
553+
n = len(graph.edges)
554+
self.matrix = [[default for _ in range(n)] for _ in range(n)]
555+
for edge in graph.iterate_edges():
556+
self.matrix[edge.start][edge.end] = merge(
557+
self.matrix[edge.start][edge.end], edge)
558+
559+
def __str__(self):
560+
return '\n'.join([' '.join(map(str, row[1:])) for row in self.matrix[1:]])
561+
562+
def __call__(self, u: int, v: int):
563+
return self.matrix[u][v]
564+
565+
def __iter__(self):
566+
return self.matrix.__iter__()

cyaron/tests/graph_test.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,24 @@ def test_DAG_boundary(self):
151151
with self.assertRaises(Exception, msg="the number of edges of connected graph must more than the number of nodes - 1"):
152152
Graph.DAG(8, 6)
153153
Graph.DAG(8, 7)
154+
155+
def test_GraphMatrix(self):
156+
g = Graph(3, True)
157+
edge_set = [(2, 3, 3), (3, 3, 1), (2, 3, 7), (2, 3, 4), (3, 2, 1), (1, 3, 3)]
158+
for u, v, w in edge_set:
159+
g.add_edge(u, v, weight=w)
160+
self.assertEqual(str(g.to_matrix()), "-1 -1 3\n-1 -1 4\n-1 1 1")
161+
self.assertEqual(str(g.to_matrix(default=0)), "0 0 3\n0 0 4\n0 1 1")
162+
# lambda val, edge: edge.weight
163+
gcd = lambda a, b: (gcd(b, a % b) if b else a)
164+
lcm = lambda a, b: a * b // gcd(a, b)
165+
merge1 = lambda v, e: v if v != -1 else e.weight
166+
merge2 = lambda val, edge: max(edge.weight, val)
167+
merge3 = lambda val, edge: min(edge.weight, val)
168+
merge4 = lambda val, edge: gcd(val, edge.weight)
169+
merge5 = lambda val, edge: lcm(val, edge.weight) if val else edge.weight
170+
self.assertEqual(str(g.to_matrix(merge=merge1)), "-1 -1 3\n-1 -1 3\n-1 1 1")
171+
self.assertEqual(str(g.to_matrix(merge=merge2)), "-1 -1 3\n-1 -1 7\n-1 1 1")
172+
self.assertEqual(str(g.to_matrix(default=9, merge=merge3)), "9 9 3\n9 9 3\n9 1 1")
173+
self.assertEqual(str(g.to_matrix(default=0, merge=merge4)), "0 0 3\n0 0 1\n0 1 1")
174+
self.assertEqual(str(g.to_matrix(default=0, merge=merge5)), "0 0 3\n0 0 84\n0 1 1")

0 commit comments

Comments
 (0)