From 5d93dcad174016b30dbdb8a39290b927533784b4 Mon Sep 17 00:00:00 2001 From: Alexandre B A Villares Date: Tue, 14 Jul 2020 20:38:22 -0300 Subject: [PATCH] 14a graphs! --- 2020/sketch_2020_07_14a/graph.py | 262 ++++++++++++++++++ .../sketch_2020_07_14a/sketch_2020_07_14a.png | Bin 0 -> 7657 bytes .../sketch_2020_07_14a.pyde | 66 +++++ 3 files changed, 328 insertions(+) create mode 100644 2020/sketch_2020_07_14a/graph.py create mode 100644 2020/sketch_2020_07_14a/sketch_2020_07_14a.png create mode 100644 2020/sketch_2020_07_14a/sketch_2020_07_14a.pyde diff --git a/2020/sketch_2020_07_14a/graph.py b/2020/sketch_2020_07_14a/graph.py new file mode 100644 index 00000000..487bc32c --- /dev/null +++ b/2020/sketch_2020_07_14a/graph.py @@ -0,0 +1,262 @@ +#*- coding: utf-8 -*- + +""" +A simple Python graph class, demonstrating the essential facts and functionalities of graphs +based on https://www.python-course.eu/graphs_python.php and https://www.python.org/doc/essays/graphs/ +""" + +class Graph(object): + + def __init__(self, graph_dict=None): + """ + Initialize a graph object with dictionary provided, + if none provided, create an empty one. + """ + if graph_dict is None: + graph_dict = {} + self.__graph_dict = graph_dict + + def __len__(self): + return len(self.__graph_dict) + + def __iter__(self): + return iter(self.__graph_dict.keys()) + + def __getitem__(self, i): + return self.__graph_dict[i] + + def vertices(self): + """Return the vertices of graph.""" + return list(self.__graph_dict.keys()) + + def edges(self): + """Return the edges of graph """ + return self.__generate_edges() + + def add_vertex(self, vertex): + """ + If the vertex "vertex" is not in self.__graph_dict, + add key "vertex" with an empty list as a value, + otherwise, do nothing. + """ + if vertex not in self.__graph_dict: + self.__graph_dict[vertex] = [] + + def add_edge(self, edge): + """ + Assuming that edge is of type set, tuple or list; + add edge between vertices. Can add multiple edges! + """ + edge = set(edge) + vertex1 = edge.pop() + if edge: + # not a loop + vertex2 = edge.pop() + else: + # a loop + vertex2 = vertex1 + if vertex1 in self.__graph_dict: + self.__graph_dict[vertex1].append(vertex2) + else: + self.__graph_dict[vertex1] = [vertex2] + + def __generate_edges(self): + """ + Generate the edges, represented as sets with one + (a loop back to the vertex) or two vertices. + """ + edges = [] + for vertex in self.__graph_dict: + for neighbour in self.__graph_dict[vertex]: + if {neighbour, vertex} not in edges: + edges.append({vertex, neighbour}) + return edges + + def __str__(self): + res = "vertices: " + for k in self.__graph_dict: + res += str(k) + " " + res += "\nedges: " + for edge in self.__generate_edges(): + res += str(edge) + " " + return res + + def find_isolated_vertices(self): + """ + Return a list of isolated vertices. + """ + graph = self.__graph_dict + isolated = [] + for vertex in graph: + print(isolated, vertex) + if not graph[vertex]: + isolated += [vertex] + return isolated + + def find_path(self, start_vertex, end_vertex, path=[]): + """ + Find a path from start_vertex to end_vertex in graph. + """ + graph = self.__graph_dict + path = path + [start_vertex] + if start_vertex == end_vertex: + return path + if start_vertex not in graph: + return None + for vertex in graph[start_vertex]: + if vertex not in path: + extended_path = self.find_path(vertex, + end_vertex, + path) + if extended_path: + return extended_path + return None + + def find_all_paths(self, start_vertex, end_vertex, path=[]): + """ + Find all paths from start_vertex to end_vertex. + """ + graph = self.__graph_dict + path = path + [start_vertex] + if start_vertex == end_vertex: + return [path] + if start_vertex not in graph: + return [] + paths = [] + for vertex in graph[start_vertex]: + if vertex not in path: + extended_paths = self.find_all_paths(vertex, + end_vertex, + path) + for p in extended_paths: + paths.append(p) + return paths + + def is_connected(self, + vertices_encountered=None, + start_vertex=None): + """Find if the graph is connected.""" + if vertices_encountered is None: + vertices_encountered = set() + gdict = self.__graph_dict + vertices = list(gdict.keys()) # "list" necessary in Python 3 + if not start_vertex: + # chosse a vertex from graph as a starting point + start_vertex = vertices[0] + vertices_encountered.add(start_vertex) + if len(vertices_encountered) != len(vertices): + for vertex in gdict[start_vertex]: + if vertex not in vertices_encountered: + if self.is_connected(vertices_encountered, vertex): + return True + else: + return True + return False + + def vertex_degree(self, vertex): + """ + Return the number of edges connecting to a vertex (the number of adjacent vertices). + Loops are counted double, i.e. every occurence of vertex in the list of adjacent vertices. + """ + adj_vertices = self.__graph_dict[vertex] + degree = len(adj_vertices) + adj_vertices.count(vertex) + return degree + + def degree_sequence(self): + """Calculates the degree sequence.""" + seq = [] + for vertex in self.__graph_dict: + seq.append(self.vertex_degree(vertex)) + seq.sort(reverse=True) + return tuple(seq) + + @staticmethod + def is_degree_sequence(sequence): + """ + Return True, if the sequence is a degree sequence (non-increasing), + otherwise return False. + """ + return all(x >= y for x, y in zip(sequence, sequence[1:])) + + def delta(self): + """Find minimum degree of vertices.""" + min = 100000000 + for vertex in self.__graph_dict: + vertex_degree = self.vertex_degree(vertex) + if vertex_degree < min: + min = vertex_degree + return min + + def Delta(self): + """Finde maximum degree of vertices.""" + max = 0 + for vertex in self.__graph_dict: + vertex_degree = self.vertex_degree(vertex) + if vertex_degree > max: + max = vertex_degree + return max + + def density(self): + """Calculate the graph density.""" + g = self.__graph_dict + V = len(g.keys()) + E = len(self.edges()) + return 2.0 * E / (V * (V - 1)) + + def diameter(self): + """Calculates the graph diameter.""" + + v = self.vertices() + pairs = [ + (v[i], + v[j]) for i in range( + len(v)) for j in range( + i + 1, + len(v) - 1)] + smallest_paths = [] + for (s, e) in pairs: + paths = self.find_all_paths(s, e) + smallest = sorted(paths, key=len)[0] + smallest_paths.append(smallest) + + smallest_paths.sort(key=len) + + # longest path is at the end of list, + # i.e. diameter corresponds to the length of this path + diameter = len(smallest_paths[-1]) - 1 + return diameter + + @staticmethod + def erdoes_gallai(dsequence): + """ + Check if Erdoes-Gallai inequality condition is fullfilled. + """ + if sum(dsequence) % 2: + # sum of sequence is odd + return False + if Graph.is_degree_sequence(dsequence): + for k in range(1, len(dsequence) + 1): + left = sum(dsequence[:k]) + right = k * (k - 1) + sum([min(x, k) for x in dsequence[k:]]) + if left > right: + return False + else: + # sequence is increasing + return False + return True + + + # Code by Eryk Kopczyński + def find_shortest_path(self, start, end): + from collections import deque + graph = self.__graph_dict + dist = {start: [start]} + q = deque(start) + while len(q): + at = q.popleft() + for next in graph[at]: + if next not in dist: + #dist[next] = [dist[at], next] + dist[next] = dist[at]+[next] # less efficient but nicer output + q.append(next) + return dist.get(end) diff --git a/2020/sketch_2020_07_14a/sketch_2020_07_14a.png b/2020/sketch_2020_07_14a/sketch_2020_07_14a.png new file mode 100644 index 0000000000000000000000000000000000000000..0d67956ddf268a427e58a52aef6e826f015be71d GIT binary patch literal 7657 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 literal 0 HcmV?d00001 diff --git a/2020/sketch_2020_07_14a/sketch_2020_07_14a.pyde b/2020/sketch_2020_07_14a/sketch_2020_07_14a.pyde new file mode 100644 index 00000000..0b2ef6a7 --- /dev/null +++ b/2020/sketch_2020_07_14a/sketch_2020_07_14a.pyde @@ -0,0 +1,66 @@ +from graph import Graph + +graph = Graph({"g": ["d", "f"], + "i": ["c"], + "c": ["i", "c", "d", "e"], + "d": ["f", "c"], + "e": ["c"], + "f": ["d", "h"], + "a": ["b"], + "h": ["f"], + "b": ["a"] + }) + + +def setup(): + size(500, 500) + noLoop() + setup_graph(graph) + fill(0) + textSize(18) + textAlign(CENTER, CENTER) + stroke(255) + strokeWeight(3) + +def draw(): + for v in grid.keys(): + xa, ya = grid[v] + for e in graph[v]: + xb, yb = grid[e] + line(xa, ya, xb, yb) + + for v in grid.keys(): + x, y = grid[v] + text(v, x, y) + + saveFrame("sketch_2020_07_14a.png") + +def setup_graph(g): + global cols, rows, grid + cols, rows = dimensionar_grade(len(g)) + w, h = width / cols, height / rows + grid = {} + v_list = g.vertices() + for c in range(cols): + for r in range(rows): + if v_list: + v = v_list.pop() + grid[v] = (w /2 + c * w, + h /2 + r * h) + + +def dimensionar_grade(n): + a = int(sqrt(n)) + b = n / a + if a * b < n: + b += 1 + print(u'{}: {} × {} ({})'.format(n, a, b, a * b)) + return a, b + +def keyPressed(): + global n + redraw() + if str(key) in '+=': + n += 1 + if key == '-' and n > 2: + n -= 1