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 00000000..0d67956d Binary files /dev/null and b/2020/sketch_2020_07_14a/sketch_2020_07_14a.png differ 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