diff --git a/.idea/agent-aspirateur.iml b/.idea/agent-aspirateur.iml index c4221c0..9095021 100644 --- a/.idea/agent-aspirateur.iml +++ b/.idea/agent-aspirateur.iml @@ -4,7 +4,7 @@ - + diff --git a/.idea/misc.xml b/.idea/misc.xml index ae34238..13fecf3 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/hoover-agent/logic.py b/hoover-agent/logic.py index 435914d..e4c4617 100644 --- a/hoover-agent/logic.py +++ b/hoover-agent/logic.py @@ -1,3 +1,13 @@ +import heapq +import math + +""" +------------------------ +- PRIMITIVES - +________________________ +""" + + class State(object): def __init__(self): raise NotImplementedError @@ -26,7 +36,7 @@ def result(self, state: State, action) -> State: def goal_test(self, state: State): return state == self.goal - def cost(self, state: State, action): + def cost(self, current_state: State, action, future_state): return 1 def heuristic(self, state: State, action): @@ -34,3 +44,102 @@ def heuristic(self, state: State, action): def __str__(self): return f"{type(self).__name__},{self.initial},{self.goal}" + + +""" +------------------------ +- DATA STRUCTS - +________________________ +""" + + +class Node(object): + def __init__(self, state, parent=None, action=None, cost: float = 0): + self.state = state + self.parent = parent + self.action = action + self.cost = cost + + def __repr__(self): + return f"{self.state}" + + def __len__(self): + return 0 if self.parent is None else (len(self.parent) + 1) + + def __lt__(self, other): + return self.cost < other.cost + + @staticmethod + def expand(problem: Problem, node): + current_state = node.state + for action in problem.actions(current_state): + child_state = problem.result(current_state, action) + cost = node.cost + problem.cost(current_state, action, child_state) + yield Node(child_state, current_state, action, cost) + + @staticmethod + def action_sequence(node): + return [] if node.parent is None else Node.action_sequence(node.parent) + [node.action] + + @staticmethod + def state_sequence(node): + return [] if node.parent is None else Node.state_sequence(node.parent) + [node.state] + + +class PriorityQueue: + """min-heap""" + + def __init__(self, elements=(), key=lambda i: i): + self.key = key # "priority value" used in priority heap evaluation + self.elements = [] # The actual priority queue + for e in elements: + self.add(e) # add starting elements, satisfying priority heap property + + def add(self, element): + indexed_element = (self.key(element), element) + heapq.heappush(self.elements, indexed_element) + + def pop(self): + return heapq.heappop(self.elements) + + def top(self): + return self.elements[0][1] + + def __len__(self): + return len(self.elements) + + +""" +------------------------ +- ALGORITHMS - +________________________ +""" + + +def bfs(problem, func): + """Best first (graph) search""" + init_node = Node(problem.initial) + frontier = PriorityQueue([init_node], key=func) + searched_nodes = {hash(problem.initial): init_node} # {hash(state):node} + while frontier: + current_node = frontier.pop() + if problem.goal_test(current_node): + return current_node + for child in Node.expand(problem, current_node): + result_state = child.state + hashed_state = hash(result_state) + if hashed_state not in searched_nodes or child.cost < searched_nodes[hashed_state].cost: + searched_nodes[hashed_state] = child + frontier.add(child) + return Node("FAILED", cost=math.inf) + + +def greedy_bfs(problem, heuristic=None): + heuristic = heuristic or problem.heuristic + return bfs(problem, heuristic) + + +def astar(problem, heuristic=None, cost=None): + heuristic = heuristic or problem.heuristic + cost = cost or problem.cost + return bfs(problem, lambda n: heuristic(n) + cost(n)) diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 37b5124..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -numpy~=1.21.2 -pretty-tables~=1.3.0 \ No newline at end of file