Skip to content

Commit

Permalink
Merge pull request #134 from weilycoder/dev4
Browse files Browse the repository at this point in the history
  • Loading branch information
Mr-Python-in-China authored Oct 3, 2024
2 parents aa1b6e7 + 011d6cc commit 476534c
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ docs/_build/
target/

# Pycharm
venv
venv/

*.DS_Store

Expand Down
153 changes: 111 additions & 42 deletions cyaron/graph.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
from .utils import *
import random
from typing import TypeVar, Callable


__all__ = ["Edge", "Graph"]


class Edge:
"""Class Edge: A class of the edge in the graph"""

def __init__(self, u, v, w):
"""__init__(self, u, v, w) -> None
Initialize a edge.
Expand All @@ -26,11 +31,13 @@ def unweighted_edge(edge):
"""unweighted_edge(edge) -> str
Return a string to output the edge without weight. The string contains the start vertex, end vertex(u,v) and splits with space.
"""
return '%d %d'%(edge.start,edge.end)
return '%d %d' % (edge.start, edge.end)


class Graph:
"""Class Graph: A class of the graph
"""

def __init__(self, point_count, directed=False):
"""__init__(self, point_count) -> None
Initialize a graph.
Expand All @@ -49,6 +56,17 @@ def edge_count(self):
cnt //= 2
return cnt

def to_matrix(self, **kwargs):
"""to_matrix(self, **kwargs) -> GraphMatrix
Convert the graph to adjacency matrix.
**kwargs(Keyword args):
int default = -1 -> the default value when the edge does not exist.
Any merge(Any, Edge)
= lambda val, edge: edge.weight
-> the mapping from the old values in matrix and the edges to the new values in matrix.
"""
return GraphMatrix(self, **kwargs)

def to_str(self, **kwargs):
"""to_str(self, **kwargs) -> str
Convert the graph to string with format. Splits with "\n"
Expand All @@ -66,7 +84,8 @@ def to_str(self, **kwargs):
edge_buf = []
for edge in self.iterate_edges():
edge_buf.append(
Edge(new_node_id[edge.start], new_node_id[edge.end], edge.weight))
Edge(new_node_id[edge.start], new_node_id[edge.end],
edge.weight))
random.shuffle(edge_buf)
for edge in edge_buf:
if not self.directed and random.randint(0, 1) == 0:
Expand Down Expand Up @@ -164,9 +183,10 @@ def tree(point_count, chain=0, flower=0, **kwargs):
if not list_like(weight_limit):
weight_limit = (1, weight_limit)
weight_gen = kwargs.get(
"weight_gen", lambda: random.randint(
weight_limit[0], weight_limit[1]))
father_gen = kwargs.get("father_gen", lambda cur: random.randrange(1, cur))
"weight_gen",
lambda: random.randint(weight_limit[0], weight_limit[1]))
father_gen = kwargs.get("father_gen",
lambda cur: random.randrange(1, cur))

if not 0 <= chain <= 1 or not 0 <= flower <= 1:
raise Exception("chain and flower must be between 0 and 1")
Expand Down Expand Up @@ -213,33 +233,35 @@ def binary_tree(point_count, left=0, right=0, **kwargs):
if not list_like(weight_limit):
weight_limit = (1, weight_limit)
weight_gen = kwargs.get(
"weight_gen", lambda: random.randint(
weight_limit[0], weight_limit[1]))
"weight_gen",
lambda: random.randint(weight_limit[0], weight_limit[1]))

if not 0 <= left <= 1 or not 0 <= right <= 1:
raise Exception("left and right must be between 0 and 1")
if left + right > 1:
raise Exception("left plus right must be smaller than 1")
can_left=[1]
can_right=[1]

can_left = [1]
can_right = [1]
graph = Graph(point_count, directed)
for i in range(2, point_count + 1):
edge_pos = random.random()
node = 0
# Left
if edge_pos < left or left + right < edge_pos <= (1.0 - left - right) / 2:
point_index = random.randint(0,len(can_left)-1)
if edge_pos < left or left + right < edge_pos <= (1.0 - left -
right) / 2:
point_index = random.randint(0, len(can_left) - 1)
node = can_left[point_index]
del_last_node = can_left.pop() # Save a copy of the last element
del_last_node = can_left.pop(
) # Save a copy of the last element
if point_index < len(can_left):
# If the chosen element isn't the last one,
# Copy the last one to the position of the chosen one
can_left[point_index] = del_last_node
# Right
else:
# elif left <= edge_pos <= left + right or (1.0 - left - right) / 2 < edge_pos < 1:
point_index = random.randint(0,len(can_right)-1)
# elif left <= edge_pos <= left + right or (1.0 - left - right) / 2 < edge_pos < 1:
point_index = random.randint(0, len(can_right) - 1)
node = can_right[point_index]
del_last_node = can_right.pop()
if point_index < len(can_right):
Expand Down Expand Up @@ -278,16 +300,17 @@ def graph(point_count, edge_count, **kwargs):
if not list_like(weight_limit):
weight_limit = (1, weight_limit)
weight_gen = kwargs.get(
"weight_gen", lambda: random.randint(
weight_limit[0], weight_limit[1]))
"weight_gen",
lambda: random.randint(weight_limit[0], weight_limit[1]))
graph = Graph(point_count, directed)
used_edges = set()
i = 0
while i < edge_count:
u = random.randint(1, point_count)
v = random.randint(1, point_count)

if (not self_loop and u == v) or (not repeated_edges and (u, v) in used_edges):
if (not self_loop and u == v) or (not repeated_edges and
(u, v) in used_edges):
# Then we generate a new pair of nodes
continue

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

self_loop = kwargs.get("self_loop", False) # DAG default has no loop
self_loop = kwargs.get("self_loop", False) # DAG default has no loop
repeated_edges = kwargs.get("repeated_edges", True)
loop = kwargs.get("loop", False)
if not repeated_edges:
Expand All @@ -332,21 +357,22 @@ def DAG(point_count, edge_count, **kwargs):
if not list_like(weight_limit):
weight_limit = (1, weight_limit)
weight_gen = kwargs.get(
"weight_gen", lambda: random.randint(
weight_limit[0], weight_limit[1]))
"weight_gen",
lambda: random.randint(weight_limit[0], weight_limit[1]))

used_edges = set()
edge_buf = list(Graph.tree(point_count, weight_gen=weight_gen).iterate_edges())
edge_buf = list(
Graph.tree(point_count, weight_gen=weight_gen).iterate_edges())
graph = Graph(point_count, directed=True)

for edge in edge_buf:
if loop and random.randint(1, 2) == 1:
edge.start, edge.end = edge.end, edge.start
graph.add_edge(edge.start, edge.end, weight=edge.weight)

if not repeated_edges:
used_edges.add((edge.start, edge.end))

i = point_count - 1
while i < edge_count:
u = random.randint(1, point_count)
Expand All @@ -355,7 +381,8 @@ def DAG(point_count, edge_count, **kwargs):
if not loop and u > v:
u, v = v, u

if (not self_loop and u == v) or (not repeated_edges and (u, v) in used_edges):
if (not self_loop and u == v) or (not repeated_edges and
(u, v) in used_edges):
# Then we generate a new pair of nodes
continue

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

self_loop = kwargs.get("self_loop", True)
repeated_edges = kwargs.get("repeated_edges", True)
Expand All @@ -397,23 +426,24 @@ def UDAG(point_count, edge_count, **kwargs):
if not list_like(weight_limit):
weight_limit = (1, weight_limit)
weight_gen = kwargs.get(
"weight_gen", lambda: random.randint(
weight_limit[0], weight_limit[1]))
"weight_gen",
lambda: random.randint(weight_limit[0], weight_limit[1]))

used_edges = set()
graph = Graph.tree(point_count, weight_gen=weight_gen, directed=False)

for edge in graph.iterate_edges():
if not repeated_edges:
used_edges.add((edge.start, edge.end))
used_edges.add((edge.end, edge.start))

i = point_count - 1
while i < edge_count:
u = random.randint(1, point_count)
v = random.randint(1, point_count)

if (not self_loop and u == v) or (not repeated_edges and (u, v) in used_edges):
if (not self_loop and u == v) or (not repeated_edges and
(u, v) in used_edges):
# Then we generate a new pair of nodes
continue

Expand Down Expand Up @@ -459,8 +489,8 @@ def hack_spfa(point_count, **kwargs):
if not list_like(weight_limit):
weight_limit = (1, weight_limit)
weight_gen = kwargs.get(
"weight_gen", lambda: random.randint(
weight_limit[0], weight_limit[1]))
"weight_gen",
lambda: random.randint(weight_limit[0], weight_limit[1]))

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

for i in range(1, half):
(x, y) = (i, i + 1)
graph.add_edge(x + (x >= point_to_skip), y +
(y >= point_to_skip), weight=weight_gen())
graph.add_edge(x + (x >= point_to_skip),
y + (y >= point_to_skip),
weight=weight_gen())
(x, y) = (i + half, i + half + 1)
graph.add_edge(x + (x >= point_to_skip), y +
(y >= point_to_skip), weight=weight_gen())
graph.add_edge(x + (x >= point_to_skip),
y + (y >= point_to_skip),
weight=weight_gen())
for i in range(1, half + 1):
(x, y) = (i, i + half)
graph.add_edge(x + (x >= point_to_skip), y +
(y >= point_to_skip), weight=weight_gen())
graph.add_edge(x + (x >= point_to_skip),
y + (y >= point_to_skip),
weight=weight_gen())

for i in range(extraedg):
u = random.randint(1, point_count)
Expand All @@ -495,3 +528,39 @@ def _calc_max_edge(point_count, directed, self_loop):
if self_loop:
max_edge += point_count
return max_edge


class GraphMatrix:
"""
Class GraphMatrix: A class of the graph represented by adjacency matrix.
*Deprecation warning: This class may be removed after a generic matrix class is implemented in the project.*
"""

T = TypeVar('T')

def __init__(self,
graph: Graph,
default: T = -1,
merge: Callable[[T, Edge],
T] = lambda val, edge: edge.weight):
"""
Args:
graph: the graph to convert,
default: the default value when the edge does not exist,
merge: the mapping from the old values in matrix and the edges to the new values in matrix.
"""
n = len(graph.edges)
self.matrix = [[default for _ in range(n)] for _ in range(n)]
for edge in graph.iterate_edges():
self.matrix[edge.start][edge.end] = merge(
self.matrix[edge.start][edge.end], edge)

def __str__(self):
return '\n'.join([' '.join(map(str, row[1:])) for row in self.matrix[1:]])

def __call__(self, u: int, v: int):
return self.matrix[u][v]

def __iter__(self):
return self.matrix.__iter__()
21 changes: 21 additions & 0 deletions cyaron/tests/graph_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,24 @@ def test_DAG_boundary(self):
with self.assertRaises(Exception, msg="the number of edges of connected graph must more than the number of nodes - 1"):
Graph.DAG(8, 6)
Graph.DAG(8, 7)

def test_GraphMatrix(self):
g = Graph(3, True)
edge_set = [(2, 3, 3), (3, 3, 1), (2, 3, 7), (2, 3, 4), (3, 2, 1), (1, 3, 3)]
for u, v, w in edge_set:
g.add_edge(u, v, weight=w)
self.assertEqual(str(g.to_matrix()), "-1 -1 3\n-1 -1 4\n-1 1 1")
self.assertEqual(str(g.to_matrix(default=0)), "0 0 3\n0 0 4\n0 1 1")
# lambda val, edge: edge.weight
gcd = lambda a, b: (gcd(b, a % b) if b else a)
lcm = lambda a, b: a * b // gcd(a, b)
merge1 = lambda v, e: v if v != -1 else e.weight
merge2 = lambda val, edge: max(edge.weight, val)
merge3 = lambda val, edge: min(edge.weight, val)
merge4 = lambda val, edge: gcd(val, edge.weight)
merge5 = lambda val, edge: lcm(val, edge.weight) if val else edge.weight
self.assertEqual(str(g.to_matrix(merge=merge1)), "-1 -1 3\n-1 -1 3\n-1 1 1")
self.assertEqual(str(g.to_matrix(merge=merge2)), "-1 -1 3\n-1 -1 7\n-1 1 1")
self.assertEqual(str(g.to_matrix(default=9, merge=merge3)), "9 9 3\n9 9 3\n9 1 1")
self.assertEqual(str(g.to_matrix(default=0, merge=merge4)), "0 0 3\n0 0 1\n0 1 1")
self.assertEqual(str(g.to_matrix(default=0, merge=merge5)), "0 0 3\n0 0 84\n0 1 1")

0 comments on commit 476534c

Please sign in to comment.