import queue # -*- coding: utf-8 -*- """Graph module. Provide an implementation of graphs with adjacency lists. In a graph, vertices are considered numbered from 0 to the order of the graph minus one. The vertex key can then be used to access its adjacency list. """ class Graph: """ Simple class for graph: adjacency lists Attributes: order (int): Number of vertices. directed (bool): True if the graph is directed. False otherwise. adjlists (List[List[int]]): Lists of connected vertices for each vertex. """ def __init__(self, order, directed, labels=None): """Init graph, allocate adjacency lists Args: order (int): Number of nodes. directed (bool): True if the graph is directed. False otherwise. labels (list[str]): optionnal vector of vertex labels """ self.order = order self.directed = directed self.adjlists = [] for _ in range(order): self.adjlists.append([]) self.labels = labels def addedge(self, src, dst): """Add egde to graph. Args: src (int): Source vertex. dst (int): Destination vertex. Raises: IndexError: If any vertex index is invalid. """ if src >= self.order or src < 0: raise IndexError("Invalid src index") if dst >= self.order or dst < 0: raise IndexError("Invalid dst index") self.adjlists[src].append(dst) if not self.directed and dst != src: self.adjlists[dst].append(src) def addvertex(self, number=1, labels=None): """Add number vertices to graph. Args: ref (Graph). number (int): Number of vertices to add. """ # Increment order and extend adjacency list self.order += number for _ in range(number): self.adjlists.append([]) if labels: self.labels += labels def removeedge(self, src, dst): """Remove egde from the graph. Args: src (int): Source vertex. dst (int): Destination vertex. Raises: IndexError: If any vertex index is invalid. """ if src >= self.order or src < 0: raise IndexError("Invalid src index") if dst >= self.order or dst < 0: raise IndexError("Invalid dst index") if dst in self.adjlists[src]: self.adjlists[src].remove(dst) if not self.directed and dst != src: self.adjlists[dst].remove(src) def sortgraph(G): """ sorts adjacency lists -> to have same results as those asked in tutorials/exams """ for i in range(G.order): G.adjlists[i].sort() def dot(G): """Dot format of graph. Args: Graph Returns: str: String storing dot format of graph. """ if G.directed: s = "digraph {\n" for x in range(G.order): if G.labels: s += " " + str(x) + '[label = "' + G.labels[x] + '"]\n' else: s += " " + str(x) + '\n' for y in G.adjlists[x]: s += str(x) + " -> " + str(y) + '\n' else: s = "graph {\n" for x in range(G.order): if G.labels: s += " " + str(x) + '[label = "' + G.labels[x] + '"]\n' else: s += " " + str(x) + '\n' for y in G.adjlists[x]: if x <= y: s += str(x) + " -- " + str(y) + '\n' return s + '}' def display(G, eng=None): """ *Warning:* Made for use within IPython/Jupyter only. eng: graphivz.Source "engine" optional argument (try "neato", "fdp", "sfdp", "circo") """ try: from graphviz import Source from IPython.display import display except: raise Exception("Missing module: graphviz and/or IPython.") display(Source(dot(G), engine = eng)) # load / save gra format def load(filename): """Build a new graph from a GRA file. Args: filename (str): File to load. Returns: Graph: New graph. Raises: FileNotFoundError: If file does not exist. """ f = open(filename) lines = f.readlines() f.close() infos = {} i = 0 while '#' in lines[i]: (key, val) = lines[i][1:].strip().split(": ") infos[key] = val i += 1 directed = bool(int(lines[i])) order = int(lines[i+1]) if infos and "labels" in infos: labels = infos["labels"].split(',') #labels is a list of str G = Graph(order, directed, labels) # a graph with labels else: G = Graph(order, directed) # a graph without labels if infos: G.infos = infos for line in lines[i+2:]: edge = line.strip().split(' ') (src, dst) = (int(edge[0]), int(edge[1])) G.addedge(src, dst) return G def save(G, fileOut): gra = "" if G.labels: lab = "#labels: " for i in range(G.order - 1): lab += G.labels[i] + ',' lab += G.labels[-1] gra += lab + '\n' gra += str(int(G.directed)) + '\n' gra += str(G.order) + '\n' for x in range(G.order): for y in G.adjlists[x]: if G.directed or x >= y: gra += str(x) + " " + str(y) + '\n' fout = open(fileOut, mode='w') fout.write(gra) fout.close() # -*- coding: utf-8 -*- """Graph module. Provide an implementation of graphs with adjacency matrix. This can also be called static implementation. In a graph, vertices are considered numbered from 0 to the order of the graph minus one. """ class GraphMat: """ Simple class for static graph. Attributes: order (int): Number of vertices. directed (bool): True if the graph is directed. False otherwise. adj (List[List[int]]): Adjacency matrix """ def __init__(self, order, directed): """ Args: order (int): Number of nodes. directed (bool): True if the graph is directed. False otherwise. """ self.order = order self.directed = directed self.adj = [[0 for j in range(order)] for i in range(order)] def addedge(self, src, dst): """Add egde to graph. Args: src (int): Source vertex. dst (int): Destination vertex. Raises: IndexError: If any vertex index is invalid. """ if src >= self.order or src < 0: raise IndexError("Invalid src index") if dst >= self.order or dst < 0: raise IndexError("Invalid dst index") self.adj[src][dst] += 1 if not self.directed and dst != src: self.adj[dst][src] += 1 def dot(G): """Dot format of graph. Args: GraphMat Returns: str: String storing dot format of graph. """ if G.directed: s = "digraph {\n" link = " -> " for x in range(G.order): for y in range(G.order): s += (str(x) + link + str(y) + '\n') * G.adj[x][y] else: s = "graph {\n" link = " -- " for x in range(G.order): for y in range(x+1): s += (str(x) + link + str(y) + '\n') * G.adj[x][y] return s + '}' def display(G, eng=None): """ *Warning:* Made for use within IPython/Jupyter only. eng: graphivz.Source "engine" optional argument (try "neato", "fdp", "sfdp", "circo") """ try: from graphviz import Source from IPython.display import display except: raise Exception("Missing module: graphviz and/or IPython.") display(Source(dot(G), engine = eng)) # load / save gra format (do not manage labels and other infos) def load(filename,): """Build a new graph from a GRA file. Args: filename (str): File to load. Returns: Graph: New graph. Raises: FileNotFoundError: If file does not exist. """ f = open(filename) directed = bool(int(f.readline())) order = int(f.readline()) g = GraphMat(order, directed) for line in f.readlines(): edge = line.strip().split(' ') (src, dst) = (int(edge[0]), int(edge[1])) g.addedge(src, dst) f.close() return g def save(G, fileOut): gra = str(int(G.directed)) + '\n' gra += str(G.order) + '\n' for x in range(G.order): if G.directed: n = G.order else: n = x + 1 for y in range(n): for i in range(G.adj[x][y]): gra += str(x) + " " + str(y) + '\n' fout = open(fileOut, mode='w') fout.write(gra) fout.close() # -*- coding: utf-8 -*- """Queue module.""" from collections import deque class Queue: """Simple class for FIFO (first-in-first-out) container.""" def __init__(self): """Init queue.""" self.elements = deque() def enqueue(self, elt): """Add an element to the queue. Args: elt (Any): Element to enqueue. """ self.elements.append(elt) def dequeue(self): """Remove and return next element from the queue. Returns: Any: Element from the queue. Raises: IndexError: If queue is empty. """ return self.elements.popleft() def isempty(self): """Check whether queue is empty. Returns: bool: True if queue is empty, False otherwise. """ return len(self.elements) == 0 G = Graph(10, False) G.adjlists = [[4, 4, 1, 3], [0], [6, 6, 6, 5, 7], [0], [0, 0, 9], [2, 7, 8], [2, 2, 2, 7], [2, 6, 5, 8], [7, 5, 8], [4]] def __smallest_level(G, M, src, perLevel): cur = Queue() cur.enqueue(src) cur.enqueue(None) M[src] = True temp = [] while not cur.isempty(): x = cur.dequeue() if x is None: if (len(temp) != 0 and temp != [src]): perLevel.append(temp) temp = [] else: temp.append(x) for adj in G.adjlists[x]: if not M[adj]: M[adj] = True cur.enqueue(adj) cur.enqueue(None) def smallest_level(G, src): M = [False] * G.order perLevel = [] __smallest_level(G, M, src, perLevel) min = perLevel[0] for element in perLevel: if len(element) < len(min): min = element return min smallest_level(G, 0)