Skip to content

Commit 7ff571b

Browse files
authored
Miguel 3.2 - Stack Min [Python] (#83)
1 parent 529c17a commit 7ff571b

File tree

1 file changed

+248
-0
lines changed

1 file changed

+248
-0
lines changed
+248
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
"""
2+
Python version 3.7.0
3+
3.2 - Stack Min
4+
How would you design a stack which, in addition to
5+
push and pop, has a function min which returns the minimum element?
6+
Push, pop and min should all operate in O(1) time.
7+
"""
8+
9+
import copy
10+
import unittest
11+
import sys
12+
13+
from abc import abstractmethod
14+
from dataclasses import dataclass
15+
from typing import Generic, TypeVar
16+
from typing import List, Optional, Iterator
17+
18+
from typing import Protocol
19+
20+
T = TypeVar('T', bound='Comparable')
21+
22+
class Comparable(Protocol):
23+
@abstractmethod
24+
def __lt__(self, other: T) -> bool:
25+
pass
26+
27+
@dataclass
28+
class StackNode(Generic[T]):
29+
data: T
30+
next: 'Optional[StackNode[T]]'
31+
running_min: T
32+
33+
class MyStack(Generic[T]):
34+
"""Stack data structure implementation.
35+
Uses LIFO (last-in first-out) ordering.
36+
The most recent item added to the stack is
37+
the first removed. Traversal is top to bottom.
38+
"""
39+
40+
class MyStackIterator(Generic[T]):
41+
def __init__(self, top: Optional[StackNode[T]], size: int):
42+
self.index = -1
43+
self.current_node = top
44+
self._size = size
45+
46+
def __next__(self) -> T:
47+
self.index += 1
48+
if self.index == self._size or self.current_node is None:
49+
raise StopIteration
50+
n: T = self.current_node.data
51+
self.current_node = self.current_node.next
52+
return n
53+
54+
def __init__(self):
55+
self.top: Optional[StackNode[T]] = None # top is a pointer to StackNode object
56+
self._size: int = 0
57+
58+
def pop(self) -> T:
59+
"""
60+
Removes the top item from the stack
61+
62+
Raises:
63+
IndexError: raised when pop is attempted on empty stack
64+
65+
Returns:
66+
int: The data at the top of the stack
67+
"""
68+
if self.top is None:
69+
raise IndexError('Stack is Empty.')
70+
item = self.top.data
71+
self.top = self.top.next
72+
self._size -= 1
73+
return item
74+
75+
def push(self, item: T) -> None:
76+
"""
77+
Adds an item to the top of the stack
78+
Args:
79+
item (T): data we want at the top of stack
80+
"""
81+
t: StackNode = StackNode(item, None, sys.maxsize)
82+
t.next = self.top
83+
if self.top is None:
84+
t.running_min = item
85+
elif item < self.top.data: # we will assume data implements __lt__
86+
t.running_min = item
87+
else:
88+
t.running_min = self.top.running_min
89+
# print("Stack Node data = {}, local min = {}".format(item, t.running_min))
90+
self.top = t
91+
self._size += 1
92+
93+
def min(self) -> T:
94+
"""
95+
Returns the element with the lowest value in the stack
96+
97+
Returns:
98+
int: min value in stack
99+
"""
100+
if self.top is None:
101+
raise ValueError('Min does not exist yet because there are no values in the stack.')
102+
return self.top.running_min
103+
104+
def peek(self) -> T:
105+
"""
106+
Returns data at the top of the stack
107+
108+
Raises:
109+
IndexError: [description]
110+
111+
Returns:
112+
int: the value at the top of the stack
113+
"""
114+
if self.top is None:
115+
raise IndexError('Stack is Empty')
116+
return self.top.data
117+
118+
def __iter__(self) -> Iterator:
119+
"""
120+
Builds a list of the current stack state.
121+
For example, given the following stack:
122+
3 -> 2 -> 1, where 3 is the top,
123+
Expect:
124+
[3, 2, 1]
125+
Returns:
126+
List[int]: list of integers
127+
"""
128+
return self.MyStackIterator(self.top, self._size)
129+
130+
def __bool__(self) -> bool:
131+
"""
132+
True is returned when the container is not empty.
133+
From https://docs.python.org/3/reference/datamodel.html#object.__bool__ :
134+
Called to implement truth value testing and the built-in operation bool();
135+
should return False or True. When this method is not defined, len() is called,
136+
if it is defined, and the object is considered true if its result is nonzero.
137+
If a class defines neither len() nor bool(), all its instances are considered true.
138+
Returns:
139+
bool: False when empty, True otherwise
140+
"""
141+
return self._size > 0
142+
143+
def __len__(self) -> int:
144+
return self._size
145+
146+
def __str__(self):
147+
if self._size == 0:
148+
return '<Empty>'
149+
values = []
150+
n = self.top
151+
while n.next is not None:
152+
values.append(str(n.data))
153+
n = n.next
154+
values.append(str(n.data))
155+
return '->'.join(values)
156+
157+
158+
class TestMyStack(unittest.TestCase):
159+
def test_stack_push(self):
160+
s = MyStack()
161+
self.assertEqual(len(s), 0)
162+
self.assertEqual(s.top, None)
163+
s.push(2)
164+
self.assertEqual(len(s), 1)
165+
self.assertEqual(s.top.data, 2)
166+
self.assertEqual(s.top.next, None)
167+
s.push(3)
168+
self.assertEqual(len(s), 2)
169+
self.assertEqual(s.top.data, 3)
170+
self.assertEqual(s.top.next.data, 2)
171+
s.push(4)
172+
self.assertEqual(len(s), 3)
173+
self.assertEqual(s.top.data, 4)
174+
self.assertEqual(s.top.next.data, 3)
175+
self.assertEqual(list(s), [4, 3, 2])
176+
177+
# test adding different types, (float and int)
178+
s.push(1.2)
179+
self.assertEqual(len(s), 4)
180+
self.assertEqual(s.top.data, 1.2)
181+
self.assertEqual(s.top.next.data, 4)
182+
self.assertEqual(list(s), [1.2, 4, 3, 2])
183+
184+
def test_stack_peek(self):
185+
s = MyStack()
186+
with self.assertRaises(IndexError):
187+
s.peek()
188+
s.push(1)
189+
s.push(2)
190+
s.push(99)
191+
top_val = s.peek()
192+
self.assertEqual(top_val, 99)
193+
194+
def test_stack_pop(self):
195+
# first case, attempt to pop an empty stack
196+
s = MyStack()
197+
with self.assertRaises(IndexError):
198+
s.pop()
199+
s.push(1)
200+
s.push(2)
201+
s.push(3)
202+
# size is 3
203+
self.assertEqual(list(s), [3, 2, 1])
204+
val = s.pop()
205+
self.assertEqual(val, 3)
206+
self.assertEqual(s._size, 2) # size should now be 2
207+
self.assertEqual(list(s), [2, 1])
208+
209+
def test_stack_min(self):
210+
s = MyStack()
211+
s.push(9)
212+
self.assertEqual(s.min(), 9)
213+
s.push(7)
214+
self.assertEqual(s.min(), 7)
215+
s.push(-2)
216+
self.assertEqual(s.min(), -2)
217+
s.push(5)
218+
self.assertEqual(s.min(), -2)
219+
s.push(8)
220+
self.assertEqual(s.min(), -2)
221+
s.push(-99)
222+
self.assertEqual(s.min(), -99)
223+
s.push(2)
224+
self.assertEqual(s.min(), -99)
225+
s.pop()
226+
self.assertEqual(s.min(), -99)
227+
s.pop()
228+
self.assertEqual(s.min(), -2)
229+
s.pop()
230+
self.assertEqual(s.min(), -2)
231+
s.pop()
232+
self.assertEqual(s.min(), -2)
233+
s.pop()
234+
self.assertEqual(s.min(), 7)
235+
s.pop()
236+
self.assertEqual(s.min(), 9)
237+
s.pop()
238+
self.assertTrue(not s)
239+
240+
def test__bool__(self):
241+
s = MyStack()
242+
self.assertFalse(s)
243+
s.push(3)
244+
self.assertTrue(s)
245+
246+
247+
if __name__ == '__main__':
248+
unittest.main()

0 commit comments

Comments
 (0)