-1

I'm trying to implement a uniform cost search algorithm using node-edge-graph structure. And these are my classes

class Node: 
    def __init__(self, name: str, edges:list = [] ) -> None:
        self.name = name
        self.edges = edges

class Edge: 
    def __init__(self, start_node: Node, end_node: Node, weight: int) -> None:
        self.start_node = start_node
        self.end_node = end_node
        self.weight = weight

class Graph:
    def __init__(self, nodes:list = [], edges:list = []) -> None:
        self.node_list = nodes
        self.edge_list = edges

    def add_node(self, node: Node) -> None:
        self.node_list.append(node)

    def add_edge(self,start_node:Node, end_node: Node, weight: int) -> None:
        edge = Edge(start_node, end_node, weight)
        self.edge_list.append(edge)
        start_node.edges.append(edge)
        end_node.edges.append(edge)

    def get_node(self, name: str) -> Node:
        for node in self.node_list:
            if name == node.name:
                return node
            else:
                raise CityNotFoundError(name)
            
    def get_edges(self, node:str) -> list:
        for node in self.node_list:
            return node.edges

    def get_weight(self, start: str, end: str) -> int:
        start_node = self.get_node(start)
        for edge in start_node.edges:
            if edge.end_node.name == end:
                return edge.weight

I implement a build_graph(filepath:str)->Graphfunction that reads a csv file and builds a graph:

def build_graph(path: str) -> Graph:
    file = open(path, "r")
    next(file)
    graph = Graph()
    read_cities = list()
    for line in file:
        vars = line.strip().split(",")
        city1 = vars[0]
        city2 = vars[1]
        distance = int(vars[2])

        if city1 not in read_cities:
            node1 = Node(city1)
            read_cities.append(city1)
        if city2 not in read_cities:
            node2 = Node(city2)
            read_cities.append(city2)

        start_node = graph.get_node(city1)
        end_node = graph.get_node(city2)
        graph.add_edge(start_node, end_node, distance)

    return graph

Finally this is my uniform_cost_search(graph, start, end) function

def uniform_cost_search(graph: Graph, start: Node, end: Node):
    
    if start not in graph.node_list:
        raise CityNotFoundError(start.name)
    if end not in graph.node_list:
        raise CityNotFoundError(end.name)
    
    frontier = PriorityQueue()
    explored = set()
    path = list()
    frontier.put((0, start, path.append(start)))

    while not frontier.empty():
        cost, current_node, node_path = frontier.get()
        
        if current_node == end:
            return node_path, cost
        
        explored.add(current_node)

        for edge in graph.get_edges(current_node.name):
            neighbor = edge.end_node
            weight = edge.weight

            if neighbor not in explored:
                total_cost = cost + weight
                path.append(neighbor)
                frontier.put((total_cost, neighbor, path))
    
    raise CityNotFoundError(end)

When I run my code I'm having an error like this

Traceback (most recent call last):
  File "c:\Users\kamil\Desktop\PythonProjects\Project-1\last.py", line 115, in <module>
    graph = build_graph(file_path)
            ^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Users\kamil\Desktop\PythonProjects\Project-1\last.py", line 73, in build_graph
    graph.add_edge(start_node, end_node, distance)
  File "c:\Users\kamil\Desktop\PythonProjects\Project-1\last.py", line 32, in add_edge
    start_node.edges.append(edge)
AttributeError: 'NoneType' object has no attribute 'edges'
    ^^^^^^^^^^^^^^^^

Does not start_node have a list named edges that I declared as it is? Why is this happening?

pippo1980
  • 2,181
  • 3
  • 14
  • 30
  • In your `get_node` function, is the `else` indented too deeply? I assume you were going for a for-else loop, and not a single iteration if-else. – B Remmelzwaal May 16 '23 at 14:14
  • In the `build_graph` function you create the `Graph` object but then don't appear to add the nodes. When you try to add the edge `start_node` and `end_node` are None. This is just from inspection (I haven't copied and run your code) so I might be missing something... – wstk May 16 '23 at 14:19
  • cant we have the input .csv to have an idea on what is going on ? – pippo1980 May 16 '23 at 15:09
  • Of course I can provide it but I don't see a necessity. However the csv file includes city1,city2,distance A,B,95 B,C,125 A,C,155 and so on – kamilcoban May 16 '23 at 15:12
  • yep maybe to you, but others could not understand a thing about UCS [i.e. like me ;-)]. Here alink about it: https://plainenglish.io/blog/uniform-cost-search-ucs-algorithm-in-python-ec3ee03fca9f – pippo1980 May 16 '23 at 16:45
  • frontier = PriorityQueue() --> TypeError: '<' not supported between instances of 'Node' and 'Node' . frontier = Queue() --> runs – pippo1980 May 18 '23 at 10:14
  • frontier.put((0, start, path.append(start))) ----> Error path.append(start) returns None, a =path.append(start) , frontier.put((0, start, path)) – pippo1980 May 19 '23 at 21:06
  • frontier = PriorityQueue() --> TypeError: '<' not supported between instances of 'Node' and 'Node' happens when cost inserted in the PriorityQueue are already there the put algo tries to compare the rest of the tuple but can't – pippo1980 May 20 '23 at 11:21

2 Answers2

2

Don't you need to make sure you add the nodes to the graph?

if city1 not in read_cities:
    node1 = Node(city1)
    read_cities.append(city1)
    graph.add_node(node1)

Im not quite sure what read_cities is for - were you planning to collect the nodes in a list and add them in one go?

wstk
  • 1,040
  • 8
  • 14
  • I missed that thanks. Actually, I'm trying to avoid the duplications using read_cities – kamilcoban May 16 '23 at 14:36
  • 1
    OK, fair enough. You could also use some of the functionality you have built into the `Graph()` class already for this. For example, you could try to get a node for a given city name, and create and add it if it doesn't exist. – wstk May 16 '23 at 17:35
  • add_node is already a Graph method but it doesnt check for already inserted nodes, so it should accept duplicates city so far – pippo1980 May 18 '23 at 16:14
  • If you want to check for duplicates it would be better to do this as part of the `add_node` method, rather than handling it externally. – wstk May 22 '23 at 08:25
0

change if name == node.list to if name == node.name

in class Graph: get_node

with input as test_OP_2.csv:

city1,city2,distance 
A,B,95
B,C,125 
A,C,155 
A,E,50
B,E,60
A,E,55

and my super print to debug code (sorry I am really slow at this thing and dont know about uniform cost search algorithm using node-edge-graph structure) :

class Node: 
    def __init__(self, name: str, edges:list = [] ) -> None:
        self.name = name
        self.edges = edges

class Edge: 
    def __init__(self, start_node: Node, end_node: Node, weight: int) -> None:
        self.start_node = start_node
        self.end_node = end_node
        self.weight = weight

class Graph:
    def __init__(self, nodes:list = [], edges:list = []) -> None:
        self.node_list = nodes
        self.edge_list = edges

    def add_node(self, node: Node) -> None:
        self.node_list.append(node)

    def add_edge(self,start_node:Node, end_node: Node, weight: int) -> None:
        edge = Edge(start_node, end_node, weight)
        self.edge_list.append(edge)
        start_node.edges.append(edge)
        end_node.edges.append(edge)

    def get_node(self, name: str) -> Node:
        for node in self.node_list:
            
            # if name == node.list: # --> ##AttributeError: 'Node' object has no attribute 'list'
            
            if name == node.name:
                
            # if name in self.node_list : #--> AttributeError: 'NoneType' object has no attribute 'edges'
            
                return node
            else:
                # raise CityNotFoundError(name)
                
                print('error')
            
    def get_edges(self, node:str) -> list:
        for node in self.node_list:
            return node.edges
            
            # return [(i.start_node.name, i.end_node.name, i.weight) for i in node.edges]

    def get_weight(self, start: str, end: str) -> int:
        start_node = self.get_node(start)
        for edge in start_node.edges:
            if edge.end_node.name == end:
                return edge.weight

    
def build_graph(path: str) -> Graph:
    file = open(path, "r")
    next(file)
    graph = Graph()
    read_cities = list()
    for line in file:
        
        print('read_cities : ', read_cities)
        print('node_list names : ', [i.name for i in graph.node_list])
        
        var = line.strip().split(",")
        
        print(var)
        
        city1 = var[0]
        city2 = var[1]
        distance = int(var[2])
        
        print('city1 : ', city1)
        
        print('city2 : ', city2)
        
        print('distance : ', distance)
        

        
        if city1 not in read_cities:
            node1 = Node(city1)
            
            print('node1 : ', node1, type(node1), 
                  'node1.name  : ', node1.name , 'node1.edges : ',node1.edges)
            
            
            
            read_cities.append(city1)
            graph.add_node(node1)
        
           
        if city2 not in read_cities:
            node2 = Node(city2)
            
            print('node2 : ', node2, type(node2), 
                  'node2.name  : ', node2.name , 'node2.edges : ',node2.edges)
            
            
            read_cities.append(city2)
            graph.add_node(node2)

        start_node = graph.get_node(city1)
        end_node = graph.get_node(city2)

        
        print('start_node : ', start_node)
        print('end_node :', end_node)
        
        graph.add_edge(start_node, end_node, distance)

    return graph


a = build_graph('test_OP_2.csv')


print('a : ', a)

print("a.get_edges('A') : " , a.get_edges('A'))

print("a.get_weight('A', 'B')  : ", a.get_weight('A', 'B'))

print("a.get_weight('C', 'E') :  ",a.get_weight('C', 'E'))

print('a.node_list : ', [i.name for i in a.node_list])

print('a.edge_list ; ', [(i.start_node.name, i.end_node.name, i.weight) for i in a.edge_list])
print(type(a))

I get as output:

read_cities :  []
node_list names :  []
['A', 'B', '95']
city1 :  A
city2 :  B
distance :  95
node1 :  <__main__.Node object at 0x7f47d467a2e0> <class '__main__.Node'> node1.name  :  A node1.edges :  []
node2 :  <__main__.Node object at 0x7f47d467a370> <class '__main__.Node'> node2.name  :  B node2.edges :  []
error
start_node :  <__main__.Node object at 0x7f47d467a2e0>
end_node : <__main__.Node object at 0x7f47d467a370>
read_cities :  ['A', 'B']
node_list names :  ['A', 'B']
['B', 'C', '125']
city1 :  B
city2 :  C
distance :  125
node2 :  <__main__.Node object at 0x7f47d467a520> <class '__main__.Node'> node2.name  :  C node2.edges :  [<__main__.Edge object at 0x7f47d467a4c0>, <__main__.Edge object at 0x7f47d467a4c0>]
error
error
error
start_node :  <__main__.Node object at 0x7f47d467a370>
end_node : <__main__.Node object at 0x7f47d467a520>
read_cities :  ['A', 'B', 'C']
node_list names :  ['A', 'B', 'C']
['A', 'C', '155']
city1 :  A
city2 :  C
distance :  155
error
error
start_node :  <__main__.Node object at 0x7f47d467a2e0>
end_node : <__main__.Node object at 0x7f47d467a520>
read_cities :  ['A', 'B', 'C']
node_list names :  ['A', 'B', 'C']
['A', 'E', '50']
city1 :  A
city2 :  E
distance :  50
node2 :  <__main__.Node object at 0x7f47d467a6a0> <class '__main__.Node'> node2.name  :  E node2.edges :  [<__main__.Edge object at 0x7f47d467a4c0>, <__main__.Edge object at 0x7f47d467a4c0>, <__main__.Edge object at 0x7f47d467a5b0>, <__main__.Edge object at 0x7f47d467a5b0>, <__main__.Edge object at 0x7f47d467a610>, <__main__.Edge object at 0x7f47d467a610>]
error
error
error
start_node :  <__main__.Node object at 0x7f47d467a2e0>
end_node : <__main__.Node object at 0x7f47d467a6a0>
read_cities :  ['A', 'B', 'C', 'E']
node_list names :  ['A', 'B', 'C', 'E']
['B', 'E', '60']
city1 :  B
city2 :  E
distance :  60
error
error
error
error
start_node :  <__main__.Node object at 0x7f47d467a370>
end_node : <__main__.Node object at 0x7f47d467a6a0>
read_cities :  ['A', 'B', 'C', 'E']
node_list names :  ['A', 'B', 'C', 'E']
['A', 'E', '55']
city1 :  A
city2 :  E
distance :  55
error
error
error
start_node :  <__main__.Node object at 0x7f47d467a2e0>
end_node : <__main__.Node object at 0x7f47d467a6a0>
a :  <__main__.Graph object at 0x7f47d467a1c0>
a.get_edges('A') :  [<__main__.Edge object at 0x7f47d467a4c0>, <__main__.Edge object at 0x7f47d467a4c0>, <__main__.Edge object at 0x7f47d467a5b0>, <__main__.Edge object at 0x7f47d467a5b0>, <__main__.Edge object at 0x7f47d467a610>, <__main__.Edge object at 0x7f47d467a610>, <__main__.Edge object at 0x7f47d467a670>, <__main__.Edge object at 0x7f47d467a670>, <__main__.Edge object at 0x7f47d467a700>, <__main__.Edge object at 0x7f47d467a700>, <__main__.Edge object at 0x7f47d467a760>, <__main__.Edge object at 0x7f47d467a760>]
a.get_weight('A', 'B')  :  95
error
error
a.get_weight('C', 'E') :   50
a.node_list :  ['A', 'B', 'C', 'E']
a.edge_list ;  [('A', 'B', 95), ('B', 'C', 125), ('A', 'C', 155), ('A', 'E', 50), ('B', 'E', 60), ('A', 'E', 55)]
<class '__main__.Graph'>

I believe that given a = build_graph('test_OP_2.csv') having :

a.node_list :  ['A', 'B', 'C', 'E']
a.edge_list ;  [('A', 'B', 95), ('B', 'C', 125), ('A', 'C', 155), ('A', 'E', 50), ('B', 'E', 60), ('A', 'E', 55)]

is the expected result, I hope.

Need to point out that the algorithm is fouled by my input and accepts duplicates edges with different values.

Also changed vars to var as is not a good practice to use builtin functions as variable name.

EDITED :

I realized that Graph.get_node() doesnt raise exception if node requested isnt in graph, so I tried to reformulate the method as:

def get_node(self, name: str) -> Node:
        
        node = next((x for x in self.node_list if x.name == name), None)    
        
        if node != None :
            
            return node
        
        else:
            
            raise CityNotFoundError(name)
pippo1980
  • 2,181
  • 3
  • 14
  • 30