Skip to content

Commit 03a61d3

Browse files
authored
Miguel 3.4 - Queue via Stacks [Python] (#85)
1 parent 7ff571b commit 03a61d3

File tree

1 file changed

+305
-0
lines changed

1 file changed

+305
-0
lines changed
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
"""Python version 3.7.0
2+
3.4 - Queue via Stacks:
3+
Implement a MyQueue class which implements a queue using two stacks.
4+
"""
5+
import copy
6+
import unittest
7+
8+
from dataclasses import dataclass
9+
from typing import Generic, TypeVar
10+
from typing import List, Optional, Iterator
11+
12+
T = TypeVar('T')
13+
14+
@dataclass
15+
class StackNode(Generic[T]):
16+
data: T
17+
next: 'Optional[StackNode[T]]'
18+
19+
20+
class MyStack(Generic[T]):
21+
"""Stack data structure implementation.
22+
Uses LIFO (last-in first-out) ordering.
23+
The most recent item added to the stack is
24+
the first removed. Traversal is top to bottom.
25+
"""
26+
def __init__(self):
27+
self.top: Optional[StackNode[T]] = None # top is a pointer to StackNode object
28+
self._size: int = 0
29+
self.index: int = -1 # tracking current node for iterator use
30+
self.current_node: Optional[StackNode[T]] = self.top
31+
return
32+
33+
def pop(self) -> T:
34+
"""
35+
Removes the top item from the stack
36+
Raises:
37+
IndexError: raised when pop is attempted on empty stack
38+
Returns:
39+
int: The data at the top of the stack
40+
"""
41+
if self.top is None:
42+
raise IndexError('Stack is Empty.')
43+
item = self.top.data
44+
self.top = self.top.next
45+
self.current_node = self.top
46+
self._size -= 1
47+
return item
48+
49+
def push(self, item: T) -> None:
50+
"""
51+
Adds an item to the top of the stack
52+
Args:
53+
item (int): data we want at the top of stack
54+
"""
55+
t = StackNode(item, None)
56+
t.next = self.top
57+
self.top = t
58+
self.current_node = self.top
59+
self._size += 1
60+
61+
def peek(self) -> T:
62+
"""
63+
Returns data at the top of the stack
64+
Raises:
65+
IndexError: [description]
66+
Returns:
67+
int: the value at the top of the stack
68+
"""
69+
if self.top is None:
70+
raise IndexError('Stack is Empty')
71+
return self.top.data
72+
73+
def empty(self) -> None:
74+
while self._size > 0:
75+
self.pop()
76+
77+
def __iter__(self) -> Iterator:
78+
"""
79+
Builds a list of the current stack state.
80+
For example, given the following stack:
81+
3 -> 2 -> 1, where 3 is the top,
82+
Expect:
83+
[3, 2, 1]
84+
Returns:
85+
List[int]: list of integers
86+
"""
87+
return self
88+
89+
def __next__(self) -> T:
90+
self.index += 1
91+
if self.index == self._size or self.current_node is None:
92+
self.index = -1
93+
self.current_node = self.top
94+
raise StopIteration
95+
n: T = self.current_node.data
96+
self.current_node = self.current_node.next
97+
return n
98+
99+
def __bool__(self) -> bool:
100+
"""
101+
True is returned when the container is not empty.
102+
From https://docs.python.org/3/reference/datamodel.html#object.__bool__ :
103+
Called to implement truth value testing and the built-in operation bool();
104+
should return False or True. When this method is not defined, len() is called,
105+
if it is defined, and the object is considered true if its result is nonzero.
106+
If a class defines neither len() nor bool(), all its instances are considered true.
107+
Returns:
108+
bool: False when empty, True otherwise
109+
"""
110+
return self._size > 0
111+
112+
def __len__(self) -> int:
113+
return self._size
114+
115+
def __str__(self):
116+
if self._size == 0:
117+
return '<Empty>'
118+
values = []
119+
n = self.top
120+
while n.next is not None:
121+
values.append(str(n.data))
122+
n = n.next
123+
values.append(str(n.data))
124+
return '->'.join(values)
125+
126+
127+
@dataclass
128+
class QueueNode(Generic[T]):
129+
data: T
130+
next: 'Optional[QueueNode[T]]'
131+
132+
133+
class MyQueue(Generic[T]):
134+
def __init__(self):
135+
self._size = 0
136+
self.data_stack = MyStack[T]() # will be used as main data container
137+
138+
def add(self, item: T):
139+
self.data_stack.push(item)
140+
self._size += 1
141+
142+
def _produce_reversed_stack(self, input_stack: MyStack) -> MyStack:
143+
"""This function will take in a stack,
144+
and return a copy of a reversed version of that
145+
stack.
146+
147+
Args:
148+
input_stack (MyStack): Stack of type T
149+
"""
150+
temp_stack = MyStack[T]()
151+
while len(input_stack) > 0:
152+
d = input_stack.pop()
153+
temp_stack.push(d)
154+
return temp_stack
155+
156+
def _pop(self) -> T:
157+
# 1. local temp stack
158+
temp_stack: MyStack = self._produce_reversed_stack(self.data_stack)
159+
# 2. extract value at top of 2nd stack.
160+
data: T = temp_stack.pop()
161+
# 3. move remaining values back to stack one
162+
self.data_stack = self._produce_reversed_stack(temp_stack)
163+
return data
164+
165+
def remove(self) -> T:
166+
if self._size == 0:
167+
raise Exception('No values in stack to remove')
168+
# use second stack to extract item of interest
169+
data: T = self._pop()
170+
self._size -= 1
171+
return data
172+
173+
def _peek(self) -> T:
174+
# 1. local temp stack
175+
temp_stack: MyStack = self._produce_reversed_stack(self.data_stack)
176+
# 2. extract value at top of 2nd stack.
177+
data: T = temp_stack.peek()
178+
# 3. move remaining values back to stack one
179+
self.data_stack = self._produce_reversed_stack(temp_stack)
180+
return data
181+
182+
def peek(self) -> T:
183+
if self._size == 0:
184+
raise Exception('No values in stack to remove')
185+
return self._peek()
186+
187+
def is_empty(self) -> bool:
188+
return self._size == 0
189+
190+
def __len__(self) -> int:
191+
return self._size
192+
193+
def __bool__(self) -> bool:
194+
"""
195+
True is returned when the container is not empty.
196+
From https://docs.python.org/3/reference/datamodel.html#object.__bool__ :
197+
Called to implement truth value testing and the built-in operation bool();
198+
should return False or True. When this method is not defined, len() is called,
199+
if it is defined, and the object is considered true if its result is nonzero.
200+
If a class defines neither len() nor bool(), all its instances are considered true.
201+
Returns:
202+
bool: False when empty, True otherwise
203+
"""
204+
return self._size > 0
205+
206+
207+
class TestMyQueue(unittest.TestCase):
208+
def test_add(self):
209+
q = MyQueue()
210+
self.assertEqual(len(q), 0)
211+
# going to add 1, 3, 5.
212+
# FIFO, so when removing, 1 goes out first
213+
q.add(1)
214+
self.assertEqual(len(q), 1)
215+
self.assertEqual(q.peek(), 1)
216+
q.add(3)
217+
self.assertEqual(len(q), 2)
218+
self.assertEqual(q.peek(), 1)
219+
q.add(5)
220+
self.assertEqual(len(q), 3)
221+
self.assertEqual(q.peek(), 1)
222+
223+
def test_remove(self):
224+
q = MyQueue()
225+
q.add(1)
226+
q.add(2)
227+
q.add(3)
228+
val = q.remove()
229+
self.assertEqual(val, 1)
230+
val = q.remove()
231+
self.assertEqual(val, 2)
232+
val = q.remove()
233+
self.assertEqual(val, 3)
234+
self.assertRaises(Exception, lambda: q.remove())
235+
236+
def test_peek(self):
237+
q = MyQueue()
238+
self.assertRaises(Exception, lambda: q.peek())
239+
q.add(99)
240+
q.add(45)
241+
self.assertEqual(q.peek(), 99)
242+
243+
def test_is_empty(self):
244+
q = MyQueue()
245+
self.assertTrue(q.is_empty())
246+
self.assertFalse(q)
247+
q.add(100)
248+
self.assertFalse(q.is_empty())
249+
self.assertTrue(q)
250+
251+
252+
class TestMyStack(unittest.TestCase):
253+
def test_stack_push(self):
254+
s = MyStack()
255+
self.assertEqual(len(s), 0)
256+
self.assertEqual(s.top, None)
257+
s.push(2)
258+
self.assertEqual(len(s), 1)
259+
self.assertEqual(s.top.data, 2)
260+
self.assertEqual(s.top.next, None)
261+
s.push(3)
262+
self.assertEqual(len(s), 2)
263+
self.assertEqual(s.top.data, 3)
264+
self.assertEqual(s.top.next.data, 2)
265+
s.push(4)
266+
self.assertEqual(len(s), 3)
267+
self.assertEqual(s.top.data, 4)
268+
self.assertEqual(s.top.next.data, 3)
269+
l = list(s)
270+
self.assertEqual(l, [4, 3, 2])
271+
272+
def test_stack_peek(self):
273+
s = MyStack()
274+
with self.assertRaises(IndexError):
275+
s.peek()
276+
s.push(1)
277+
s.push(2)
278+
s.push(99)
279+
top_val = s.peek()
280+
self.assertEqual(top_val, 99)
281+
282+
def test_stack_pop(self):
283+
# first case, attempt to pop an empty stack
284+
s = MyStack()
285+
with self.assertRaises(IndexError):
286+
s.pop()
287+
s.push(1)
288+
s.push(2)
289+
s.push(3)
290+
# size is 3
291+
self.assertEqual(list(s), [3, 2, 1])
292+
val = s.pop()
293+
self.assertEqual(val, 3)
294+
self.assertEqual(len(s), 2) # size should now be 2
295+
self.assertEqual(list(s), [2, 1])
296+
297+
def test__bool__(self):
298+
s = MyStack()
299+
self.assertFalse(s)
300+
s.push(3)
301+
self.assertTrue(s)
302+
303+
304+
if __name__ == '__main__':
305+
unittest.main()

0 commit comments

Comments
 (0)