1+ from typing import TypeVar , Iterable , Sequence , Generic , List , Callable , Set , Deque , Dict , Any , Optional
2+ from typing_extensions import Protocol
3+ from functools import total_ordering
4+ from heapq import heappush , heappop
5+
6+ T = TypeVar ('T' )
7+
8+
9+ def linear_contains (iterable : Iterable [T ], key : T ) -> bool :
10+ for item in iterable :
11+ if item == key :
12+ return True
13+ return False
14+
15+
16+ C = TypeVar ("C" , bound = "Comparable" )
17+
18+
19+ class Comparable (Protocol ):
20+ def __eq__ (self , other : Any ) -> bool :
21+ ...
22+
23+ def __lt__ (self : C , other : C ) -> bool :
24+ ...
25+
26+ def __gt__ (self : C , other : C ) -> bool :
27+ return (not self < other ) and self != other
28+
29+ def __le__ (self : C , other : C ) -> bool :
30+ return self < other or self == other
31+
32+ def __ge__ (self : C , other : C ) -> bool :
33+ return not self < other
34+
35+
36+ def binary_contains (sequence : Sequence [C ], key : C ) -> bool :
37+ low : int = 0
38+ high : int = len (sequence ) - 1
39+ while low <= high : # while there is still a search space
40+ mid : int = (low + high ) // 2
41+ if sequence [mid ] < key :
42+ low = mid + 1
43+ elif sequence [mid ] > key :
44+ high = mid - 1
45+ else :
46+ return True
47+ return False
48+
49+
50+ class Stack (Generic [T ]):
51+ def __init__ (self ) -> None :
52+ self ._container : List [T ] = []
53+
54+ @property
55+ def empty (self ) -> bool :
56+ return not self ._container # not is true for empty container
57+
58+ def push (self , item : T ) -> None :
59+ self ._container .append (item )
60+
61+ def pop (self ) -> T :
62+ return self ._container .pop () # LIFO
63+
64+ def __repr__ (self ) -> str :
65+ return repr (self ._container )
66+
67+
68+ class Node (Generic [T ]):
69+ def __init__ (self , state : T , parent : Optional ['Node' ], cost : float = 0.0 , heuristic : float = 0.0 ) -> None :
70+ self .state : T = state
71+ self .parent : Optional ['Node' ] = parent
72+ self .cost : float = cost
73+ self .heuristic : float = heuristic
74+
75+ def __lt__ (self , other : 'Node' ) -> bool :
76+ return (self .cost + self .heuristic ) < (other .cost + other .heuristic )
77+
78+
79+ def dfs (initial : T , goal_test : Callable [[T ], bool ], successors : Callable [[T ], List [T ]]) -> Optional [Node [T ]]:
80+ # frontier is where we've yet to go
81+ frontier : Stack [Node [T ]] = Stack ()
82+ frontier .push (Node (initial , None ))
83+ # explored is where we've been
84+ explored : Set [T ] = {initial }
85+
86+ # keep going while there is more to explore
87+ while not frontier .empty :
88+ current_node : Node [T ] = frontier .pop ()
89+ current_state : T = current_node .state
90+ # if we found the goal, we're done
91+ if goal_test (current_state ):
92+ return current_node
93+ # check where we can go next and haven't explored
94+ for child in successors (current_state ):
95+ if child in explored : # skip children we already explored
96+ continue
97+ explored .add (child )
98+ frontier .push (Node (child , current_node ))
99+ return None # went through everything and never found goal
100+
101+
102+ def node_to_path (node : Node [T ]) -> List [T ]:
103+ path : List [T ] = [node .state ]
104+ # work backwards from end to front
105+ while node .parent is not None :
106+ node = node .parent
107+ path .append (node .state )
108+ path .reverse ()
109+ return path
110+
111+
112+ class Queue (Generic [T ]):
113+ def __init__ (self ) -> None :
114+ self ._container : Deque [T ] = Deque ()
115+
116+ @property
117+ def empty (self ) -> bool :
118+ return not self ._container # not is true for empty container
119+
120+ def push (self , item : T ) -> None :
121+ self ._container .append (item )
122+
123+ def pop (self ) -> T :
124+ return self ._container .popleft () # FIFO
125+
126+ def __repr__ (self ) -> str :
127+ return repr (self ._container )
128+
129+
130+ def bfs (initial : T , goal_test : Callable [[T ], bool ], successors : Callable [[T ], List [T ]]) -> Optional [Node [T ]]:
131+ # frontier is where we've yet to go
132+ frontier : Queue [Node [T ]] = Queue ()
133+ frontier .push (Node (initial , None ))
134+ # explored is where we've been
135+ explored : Set [T ] = {initial }
136+
137+ # keep going while there is more to explore
138+ while not frontier .empty :
139+ current_node : Node [T ] = frontier .pop ()
140+ current_state : T = current_node .state
141+ # if we found the goal, we're done
142+ if goal_test (current_state ):
143+ return current_node
144+ # check where we can go next and haven't explored
145+ for child in successors (current_state ):
146+ if child in explored : # skip children we already explored
147+ continue
148+ explored .add (child )
149+ frontier .push (Node (child , current_node ))
150+ return None # went through everything and never found goal
151+
152+
153+ # Upon each pop() returns the item with the highest lowest priority
154+ # unless max is set to true
155+ class PriorityQueue (Generic [T ]):
156+ def __init__ (self ) -> None :
157+ self ._container : List [T ] = []
158+
159+ @property
160+ def empty (self ) -> bool :
161+ return not self ._container # not is true for empty container
162+
163+ def push (self , item : T ) -> None :
164+ heappush (self ._container , item ) # priority determined by order of item
165+
166+ def pop (self ) -> T :
167+ return heappop (self ._container ) # out by priority
168+
169+ def __repr__ (self ) -> str :
170+ return repr (self ._container )
171+
172+
173+ def astar (initial : T , goal_test : Callable [[T ], bool ], successors : Callable [[T ], List [T ]], heuristic : Callable [[T ], float ]) -> Optional [Node [T ]]:
174+ # frontier is where we've yet to go
175+ frontier : PriorityQueue [Node [T ]] = PriorityQueue ()
176+ frontier .push (Node (initial , None , 0.0 , heuristic (initial )))
177+ # explored is where we've been
178+ explored : Dict [T , float ] = {initial : 0.0 }
179+
180+ # keep going while there is more to explore
181+ while not frontier .empty :
182+ current_node : Node [T ] = frontier .pop ()
183+ current_state : T = current_node .state
184+ # if we found the goal, we're done
185+ if goal_test (current_state ):
186+ return current_node
187+ # check where we can go next and haven't explored
188+ for child in successors (current_state ):
189+ new_cost : float = current_node .cost + 1 # 1 assumes a grid, need a cost function for more sophisticated apps
190+
191+ if child not in explored or explored [child ] > new_cost :
192+ explored [child ] = new_cost
193+ frontier .push (Node (child , current_node , new_cost , heuristic (child )))
194+ return None # went through everything and never found goal
195+
196+
197+ if __name__ == "__main__" :
198+ print (linear_contains ([1 , 5 , 15 , 15 , 15 , 15 , 20 ], 5 )) # True
199+ print (binary_contains (["a" , "d" , "e" , "f" , "z" ], "f" )) # True
200+ print (binary_contains (["mark" , "sarah" , "john" , "ronald" ], "sheila" )) # False
0 commit comments