I'm trying to make an undirected graph from an adjacency list to practice the Karger's Min Cut algorithm. The following is my code
class Vertex(object):
'''Represents a vertex, with the indices of edges
incident on it'''
def __init__(self,name,edgeIndices=[]):
self.name = name
self.edgeIndices = edgeIndices
def getName(self):
return self.name
def addEdge(self,ind):
self.edgeIndices.append(ind)
def getEdges(self):
return self.edgeIndices
def __eq__(self,other):
return self.name == other.name
class Edge(object):
'''Represents an edge with the indices of its endpoints'''
def __init__(self,ends):
self.ends = ends
def getEnds(self):
return self.ends
def __eq__(self,other):
return (self.ends == other.ends)\
or ((self.ends[1],self.ends[0]) == other.ends)
class Graph(object):
def __init__(self,vertices,edges):
self.edges = edges
self.vertices = vertices
def createGraph(filename):
'''Input: Adjacency list
Output: Graph object'''
vertices = []
edges = []
with open(filename) as f:
for line in f:
elements = line.split()
newVert = Vertex(elements[0])
if newVert not in vertices:
vertices.append(newVert)
for verts in elements[1:]:
otherVert = Vertex(verts)
if otherVert not in vertices:
vertices.append(otherVert)
end1 = vertices.index(newVert)
end2 = vertices.index(otherVert)
newEdge = Edge((end1,end2))
if newEdge not in edges:
edges.append(newEdge)
newVert.addEdge(edges.index(newEdge))
return Graph(vertices,edges)
Suppose the adjacency list is as follows with vertices represented by integers
1 -> 2,3,4
2 -> 1,3
3 -> 1,2,4
4 -> 1,3
In total, this graph will have five edges, so the length of list holding indices of edges a vertex is associated with can't more than 5 long.
For instance, I expect the vertex '2' to have indices of just two edges, i.e. edges with vertices 1 and 3. Instead, what I get is [0, 1, 2, 3, 0, 2, 1, 3].
I need help to figure out what is going wrong.
First error comes from the Vertex init. When passing a list as default argument, Python instantiates it once, and share this instance with all future instances of Vertex.
Pass None, and use a local list if no list is given.
class Vertex(object):
def __init__(self,name,edgeIndices=None):
self.name = name
self.edgeIndices = edgeIndices if edgeIndices else []
In the createGraph method, when the vertex already exists in the graph you need to use it. See the added else: newVert = ...
You also seem to have an issue with the ligne splitting. See the iteration over elements[2].split(',').
def createGraph(filename):
'''Input: Adjacency list
Output: Graph object'''
vertices = []
edges = []
with open(filename) as f:
for line in f:
elements = line.split()
newVert = Vertex(elements[0])
if newVert not in vertices:
vertices.append(newVert)
else:
newVert = vertices[vertices.index(newVert)]
for verts in elements[2].split(','):
otherVert = Vertex(verts)
if otherVert not in vertices:
vertices.append(otherVert)
end1 = vertices.index(newVert)
end2 = vertices.index(otherVert)
newEdge = Edge((end1,end2))
if newEdge not in edges:
edges.append(newEdge)
newVert.addEdge(edges.index(newEdge))
return Graph(vertices,edges)
As a side note, I would try to use a dict to store the vertices (and edges) and do the lookup. List.index is used a lot, and you may create a lot of objects for nothing.
I would recommend to take a look at Dict, OrderedDict, Linked List based graph implementations. The are far more effective then based on lists and indexes.
To make you code work you can do the following:
Change a Vertex to avoid issue described in previous answer:
class Vertex(object):
def __init__(self,name, edgeIndices=None):
self.name = name
self.edgeIndices = edgeIndices or []
Let the graph do some work:
class Graph(object):
def __init__(self):
self.edges = []
self.vertices = []
def add_vertex(self, name):
vertex = Vertex(name)
if vertex not in self.vertices:
self.vertices.append(vertex)
def add_edge(self, *names):
self._add_vertices(names)
edge = self._add_edge(names)
self._update_vertices_links(edge, names)
def get_vertex_index(self, name):
vertex = Vertex(name)
return self.vertices.index(vertex)
def get_vertex(self, name):
return self.vertices[self.get_vertex_index(name)]
def _update_vertices_links(self, edge, names):
for name in names:
vertex = self.get_vertex(name)
vertex.addEdge(self.edges.index(edge))
def _add_edge(self, names):
edge = Edge((self.get_vertex_index(names[0]), self.get_vertex_index(names[1])))
if edge not in self.edges:
self.edges.append(edge)
return edge
def _add_vertices(self, names):
for name in names:
self.add_vertex(name)
def __repr__(self):
return "Vertices: %s\nEdges: %s" % (self.vertices, self.edges)
Create Graph:
def createGraph(filename):
with open(filename) as f:
graph = Graph()
for line in f:
elements = line.strip().split()
graph.add_vertex(elements[0])
for element in elements[2].split(","):
graph.add_edge(elements[0], element)
return graph
Run it:
graph = createGraph('input.txt')
print graph
Output for your input:
Vertices: [<Name:1 Edges:[0, 1, 2]>, <Name:2 Edges:[0, 3]>, <Name:3 Edges:[1, 3, 4]>, <Name:4 Edges:[2, 4]>]
Edges: [(0, 1), (0, 2), (0, 3), (1, 2), (2, 3)]
Related
I'm trying to generate a random graph by first adding 20 vertices to it and then randomly match each of these with another vertex. The adjacency list is a dictionary.
When I try to add these to the graph I just get a key error and I'm not sure why.
Is the dictionary not being filled correctly?
Below is the code.
'''
class Vertex:
# Constructor for a new Vertex object. All vertex objects
# start with a distance of positive infinity.
def __init__(self, label):
self.label = label
self.distance = float('inf')
self.pred_vertex = None
class Graph:
def __init__(self):
self.adjacency_list = {}
self.edge_weights = {}
def add_vertex(self, new_vertex):
self.adjacency_list[new_vertex] = []
def add_directed_edge(self, from_vertex, to_vertex, weight = 1.0):
self.edge_weights[(from_vertex, to_vertex)] = weight
self.adjacency_list[from_vertex].append(to_vertex)
def add_undirected_edge(self, vertex_a, vertex_b, weight = 1.0):
self.add_directed_edge(vertex_a, vertex_b, weight)
self.add_directed_edge(vertex_b, vertex_a, weight)
g = Graph()
l = []
#add vertices 0-19 to the graph
for i in range(20):
l.append(i)
s = str(i)
g.add_vertex(Vertex(s))
#connect a random vertex (0-19) with another random vertex
edges = [[random.choice(l), random.randrange(0, 20)] for i in range(10)]
#add these random edges to the graph
for i in range(10):
g.add_undirected_edge(str(edges[i][0]), str(edges[i][1]), random.randint(0,100))
'''
Thank you!
The keys in your adjacency list are not numbers, but Vertex objects. That is why you are getting a key error, you are trying to look up the vertex using an str(integer), but you need to pass a Vertex object.
The simplest solution is to remove the Vertex class and just use the strings of the integers for your vertex labels and adjacency keys. That would reduce your code to this:
class Graph:
def __init__(self):
self.adjacency_list = {}
self.edge_weights = {}
def add_vertex(self, new_vertex):
self.adjacency_list[new_vertex] = []
def add_directed_edge(self, from_vertex, to_vertex, weight = 1.0):
self.edge_weights[(from_vertex, to_vertex)] = weight
self.adjacency_list[from_vertex].append(to_vertex)
def add_undirected_edge(self, vertex_a, vertex_b, weight = 1.0):
self.add_directed_edge(vertex_a, vertex_b, weight)
self.add_directed_edge(vertex_b, vertex_a, weight)
g = Graph()
l = []
#add vertices 0-19 to the graph
for i in range(20):
l.append(i)
s = str(i)
g.add_vertex(s)
#connect a random vertex (0-19) with another random vertex
edges = [[random.choice(l), random.randrange(0, 20)] for i in range(10)]
#add these random edges to the graph
for i in range(10):
g.add_undirected_edge(str(edges[i][0]), str(edges[i][1]), random.randint(0,100))
Another option is to store the adjacency using the string labels, but also store a mapping between the labels and the Vertex objects so they can be retrieved.
class Vertex:
# Constructor for a new Vertex object. All vertex objects
# start with a distance of positive infinity.
def __init__(self, label):
self.label = label
self.distance = float('inf')
self.pred_vertex = None
def __repr__(self):
return f'<Vertex {self.label!r}>'
class Graph:
def __init__(self):
self.adjacency_list = {}
self.vertex_map = {}
self.edge_weights = {}
def add_vertex(self, vertex_label):
self.adjacency_list[vertex_label] = []
self.vertex_map[vertex_label] = Vertex(vertex_label)
def add_directed_edge(self, from_vertex, to_vertex, weight = 1.0):
self.edge_weights[(from_vertex, to_vertex)] = weight
self.adjacency_list[from_vertex].append(to_vertex)
def add_undirected_edge(self, vertex_a, vertex_b, weight = 1.0):
self.add_directed_edge(vertex_a, vertex_b, weight)
self.add_directed_edge(vertex_b, vertex_a, weight)
g = Graph()
l = []
#add vertices 0-19 to the graph
for i in range(20):
l.append(i)
s = str(i)
g.add_vertex(s)
#connect a random vertex (0-19) with another random vertex
edges = [[random.choice(l), random.randrange(0, 20)] for i in range(10)]
#add these random edges to the graph
for i in range(10):
g.add_undirected_edge(str(edges[i][0]), str(edges[i][1]), random.randint(0,100))
I have a problem with an algorithmic task. There is content of it: "You have ten points on a plane and none three of them are collinear, each pair of different points is connected by line segment, which is green or blue. Calculate how many triangles have sides only in one colour." I tried a solution with n-ary trees but I get repeated triangles with cyclic permutations of integers on the result list.
Patryk, the problem is solvable with n-trees. However, to avoid cyclic permutations you need to skip symmetric line segments. If you create a line segment from 0 -> 1 you do not need to create a segment from 1 -> 0. Below is a complete code which solves the problem with n-trees with the recursive depth-first search. Excuse for Polish names of classes and methods. The interface is in English however. If you analyze the code you will get the point surely.
from random import choice
import copy
class Punkt:
def __init__(self, numer):
self.__numer = numer
self.__odcinki = {}
def dodaj_odcinek_wychodzący(self, punkt_docelowy, kolor):
self.__odcinki[punkt_docelowy] = Odcinek(self.__numer, punkt_docelowy, kolor)
def wez_odcinek_do_punktu_docelowego(self, punkt_docelowy):
return (punkt_docelowy, self.__odcinki[punkt_docelowy].wez_kolor())
def liczba_odcinkow(self):
return len(self.__odcinki)
def wez_kolor_punktu_docelowego(self, punkt_docelowy):
return self.__odcinki[punkt_docelowy].wez_kolor()
def lista_punktow_docelowych(self):
return self.__odcinki.keys()
class Odcinek:
def __init__(self, punkt_zrodlowy, punkt_docelowy, kolor):
self.__punkt_zrodlowy = punkt_zrodlowy
self.__punkt_docelowy = punkt_docelowy
self.__kolor = kolor
def wez_kolor(self):
return self.__kolor
class Structure:
def __init__(self, liczba_punktow=10):
self.__punkty = [Punkt(i)
for i in range(liczba_punktow)]
for i in range(liczba_punktow):
for j in range(i + 1, liczba_punktow):
self.__punkty[i].dodaj_odcinek_wychodzący(j, choice(["green", "blue"]))
# for j in range(liczba_punktow):
# for i in range (j + 1, liczba_punktow):
# self.__punkty[j].dodaj_odcinek_wychodzący(*(self.__punkty[j].wez_odcinek_do_punktu_docelowego(i)))
def wez_punkt(self, numer):
return self.__punkty[numer]
def wez_liczbe_punktow(self):
return len(self.__punkty)
class Search:
def __init__(self, struktura):
self.__s = struktura
def wez_liczbe_punktow(self):
return self.__s.wez_liczbe_punktow()
def wez_punkt(self, numer):
return self.__s.wez_punkt(numer)
def szukaj(self, kolor="green"):
self.__szukany_kolor = kolor
lista_trojkatow = []
liczba_trojkatow = 0
wszystkie_trojkaty = []
for i in range(self.wez_liczbe_punktow()):
lista_odwiedzonych_punktow = [i]
lista_trojkatow = self.szukaj_z_punktu(i,lista_odwiedzonych_punktow,lista_trojkatow)
return len(lista_trojkatow), lista_trojkatow
def wez_szukany_kolor(self):
return self.__szukany_kolor
def szukaj_z_punktu(self, numer, trojkat, lista_trojkatow):
if len(trojkat) == 3: # jeżeli zebraliśmy już trzy punkty to brakuje tylko zamykającego, czwartego
if self.wez_punkt(trojkat[0]).wez_kolor_punktu_docelowego(
trojkat[-1]) == self.wez_szukany_kolor(): # sprawdź czy do punktu zamykającego prowadzi odcinek o szukanym kolorze
trojkat.append(trojkat[0]) # dodaj punkt zamykajacy do trójkąta
lista_trojkatow.append(trojkat) # dodaj trojkąt do listy trójkątów
# return lista_trojkatow # zwróć liste trójkątów obliczonych dotychczas
else:
potomkowie = []
for punkt_docelowy in self.wez_punkt(numer).lista_punktow_docelowych():
if self.wez_punkt(numer).wez_kolor_punktu_docelowego(punkt_docelowy) == self.wez_szukany_kolor():
potomkowie.append(punkt_docelowy)
for potomek in potomkowie:
trojkat_kopia = copy.copy(trojkat)
trojkat_kopia.append(potomek)
lista_trojkatow = self.szukaj_z_punktu(potomek, trojkat_kopia, lista_trojkatow)
return lista_trojkatow
if __name__ == "__main__":
s = Structure()
for source_point in range(s.wez_liczbe_punktow()):
for destination_point in s.wez_punkt(source_point).lista_punktow_docelowych():
print(f"{source_point} -> {destination_point} = {s.wez_punkt(source_point).wez_kolor_punktu_docelowego(destination_point)}")
color = "green"
searching = Search(s)
number_of_triangles, all_triangles = searching.szukaj("green")
print(f"Number of triangles of color {color} = {number_of_triangles}")
print(f"List of all triangles: {all_triangles}")
I'm learning python and this an exercise, I'm trying to build graph data.
class Vertex:
def __init__(self, value):
self.value = value
self.edges = {}
def add_edge(self, vertex, weight = 0):
self.edges[vertex] = weight
def get_edges(self):
return list(self.edges.keys())
class Graph:
def __init__(self, directed = False):
self.graph_dict = {}
self.directed = directed
def add_vertex(self, vertex):
self.graph_dict[vertex.value] = vertex
def add_edge(self, from_vertex, to_vertex, weight = 0):
self.graph_dict[from_vertex.value].add_edge(to_vertex.value, weight)
if not self.directed:
self.graph_dict[to_vertex.value].add_edge(from_vertex.value, weight)
In graph class in add_edge method i find the following code complex and I don't understand how it works.
self.graph_dict[from_vertex.value].add_edge(to_vertex.value, weight)
I don't know what make you problem but you can write it in two lines
#get some vertex from ditcionary
some_vertex = self.graph_dict[from_vertex.value]
#and add new edge to this vertex
some_vertex.add_edge(to_vertex.value, weight)
So I am implementing BFS on a Graph to detect all the cycles. I implemented the graph via an adjacency list. But when I run my code I get the following error
Traceback (most recent call last):
File "C:\Python27\Data Structures\Graph\bfstree.py", line 228, in <module>
main()
File "C:\Python27\Data Structures\Graph\bfstree.py", line 223, in main
traverse(g.getVertex(2))
File "C:\Python27\Data Structures\Graph\bfstree.py", line 168, in traverse
while (x.getPred()):
AttributeError: 'list' object has no attribute 'getPred'
So the problem occurs when I call the traverse() function.
Here is my main function
def main():
g = Graph()
for i in range(1,9):
g.addVertex(i)
g.addEdge(1,2)
g.addEdge(1,4)
g.addEdge(1,8)
g.addEdge(2,3)
g.addEdge(2,1)
g.addEdge(3,2)
g.addEdge(3,4)
g.addEdge(3,7)
g.addEdge(3,8)
g.addEdge(4,1)
g.addEdge(4,3)
g.addEdge(4,5)
g.addEdge(5,4)
g.addEdge(5,6)
g.addEdge(5,7)
g.addEdge(6,5)
g.addEdge(6,7)
g.addEdge(7,3)
g.addEdge(7,6)
g.addEdge(7,5)
g.addEdge(8,3)
g.addEdge(8,1)
for v in g:
for w in v.getConnections():
print("(%s,%s)"%(v.getId(),w.getId()))
print("\nDoing BFS...")
bfs_tree(g,g.getVertex(1))
a = g.getVertex(2)
print(type(a))
traverse(g.getVertex(2))
main()
Here is the traverse function:
def traverse(y):
x = y
while (x.getPred()):
print(x.getId())
x = x.getPred()
print(x.getId())
Here is the adjacency list implementation of the graph:
class Graph:
def __init__(self):
self.vertList = {} #this is the masterlist
self.numVertices = 0
def addVertex(self,key): #turn something into a Vertex object
self.numVertices = self.numVertices + 1
newVertex = Vertex(key)
self.vertList[key] = newVertex #maps vertex names to vertex objects
return newVertex
def getVertex(self,n):
if n in self.vertList:
return self.vertList[n] #returns the Vertex object
else:
return None
def __contains__(self,n):#tweak the built-in operator 'in'(containment check)
return n in self.vertList
def addEdge(self,f,t,cost = 0):
if f not in self.vertList: #if f is not a node in the graph
nv = self.addVertex(f)
if t not in self.vertList: #if t is not a node in the graph
nv = self.addVertex(t)
self.vertList[f].addNeighbor(self.vertList[t], cost)
def getVertices(self):
return self.vertList.keys()
def __iter__(self): # iterate over Vertex objects over the Graph
return iter(self.vertList.values())
class Vertex:
def __init__(self,key):
self.id = key
self.connectedTo={} #dictionary which contains all the other vertices it is connected to
self.pred = [] #for BFS tree / a list because we are dealing with cycles
self.color = "white" #for BFS tree
def addNeighbor(self,nbr,weight=0):
self.connectedTo[nbr] = weight #nbr is another Vertex object
def __str__(self):
#TODO: lookup how that for loop works
return str(self.id) + "connected to " + str([x.id for x in self.connectedTo])
def getConnections(self):
return self.connectedTo.keys()
def getId(self):
return self.id
def getWeight(self,nbr):
return self.connectedTo[nbr]
def getColor(self):
return self.color
def setColor(self,color):
self.color = color
def setPred(self,node):
self.pred.append(node)
def getPred(self):
if len(self.pred)>1:
return self.pred
elif len(self.pred) == 0:
return self.pred[0]
else:
return self.pred
Why is it saying that g.getVertex(2) is a list object? I am pretty sure that it's a Vertex object. I even printed out the type in the main function and it says it's an instance and not a list object.
You replace x with the result of x.getPred() here:
while (x.getPred()):
print(x.getId())
x = x.getPred()
x.getPred() returns self.pred:
def getPred(self):
if len(self.pred)>1:
return self.pred
elif len(self.pred) == 0:
return self.pred[0]
else:
return self.pred
(Note that for len(self.pred) == 0 you try to return self.pred[0], which will raise an IndexError exception).
self.pred is a list:
class Vertex:
def __init__(self,key):
# ...
self.pred = [] #for BFS tree / a list because we are dealing with cycles
So you replaced x with a list object, then loop back and call x.getPred() on that list object.
x = x.getPred() is the problem. The first check in the while loop is fine, but it breaks after x is updated the first time, then rechecked.
As implemented, getPred is returning self.pred (the only case where it returns a value from self.pred instead of the whole thing is broken; the length is 0, and you index, so it will raise IndexError). self.pred is a list.
How can I use the depth first search function (dfs) to produce a topological sort?
This is my code:
class Vertex:
def __init__(self,key):
self.id = key
self.connectedTo = {}
def addNeighbor(self,nbr,weight=0):
self.connectedTo[nbr] = weight
def __str__(self):
return str(self.id) + ' connectedTo: ' + str([x.id for x in self.connectedTo])
def getConnections(self):
return self.connectedTo.keys()
def getId(self):
return self.id
def getWeight(self,nbr):
return self.connectedTo[nbr]
class Graph:
def __init__(self):
self.vertList = {}
self.numVertices = 0
def addVertex(self,key):
self.numVertices = self.numVertices + 1
newVertex = Vertex(key)
self.vertList[key] = newVertex
return newVertex
def getVertex(self,n):
if n in self.vertList:
return self.vertList[n]
else:
return None
def __contains__(self,n):
return n in self.vertList
def addEdge(self,f,t,cost=0):
if f not in self.vertList:
nv = self.addVertex(f)
if t not in self.vertList:
nv = self.addVertex(t)
self.vertList[f].addNeighbor(self.vertList[t], cost)
def getVertices(self):
return self.vertList.keys()
def __iter__(self):
return iter(self.vertList.values())
class DFSGraph(Graph):
def __init__(self):
super().__init__()
self.time = 0
def dfs(self):
for aVertex in self:
aVertex.setColor('white')
aVertex.setPred(-1)
for aVertex in self:
if aVertex.getColor() == 'white':
self.dfsvisit(aVertex)
While there are different ways to obtain the topological sort of a given graph, the topological sort of a graph can be obtained using DFS with some bookkeeping. Of course, for there to be a topological ordering of a graph in the first place, the graph must be a directed acyclic graph (DAG). A way to know if a graph is a DAG is if you can find a vertex with no incoming edges.
A helpful visual I found from https://www.techiedelight.com/topological-sorting-dag/ shows that a back edge creates a cycle in the graph. In topological sort we are attempting to order the edges from largest departure time to smallest departure time.
The reason DFS works to find Topological sort is that if DFS is run on a DAG, the search will not find any back edges because there are no cycles by definition. That means there are no edges in a tree (U,V) where the finishing time of V is greater than the finishing time of U. DFS on a DAG has no back edges so all the edges U,V finish with U < V. Therefore, if we run through DFS and place our nodes that finished first into a stack, and then pop out the items and print them, we get the topological ordering.
Since this is simply DFS with an extra data structure, the runtime would simply be O(V + E).