Skip to content

Commit 0a9438c

Browse files
authored
Miguel 3.3 - Stack of Plates [Python] (#84)
1 parent 8259c4b commit 0a9438c

File tree

1 file changed

+375
-0
lines changed

1 file changed

+375
-0
lines changed
Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
"""Python version 3.7.0
2+
3.3 - Stack of Plates
3+
Imagine a (literal) stack of plates. If the stack gets too high, it might topple.
4+
Therefore, in real life, we would likely start a new stack when the previous stack
5+
exceeds some threshold. Implement a data structure SetOfStacks that mimics this.
6+
SetOfStacks should be composed of several stacks and should create a new stack once
7+
the previous one exceeds capacity. SetOfStacks.push() and SetOfStacks.pop() should
8+
behave identically to a single stack (that is, pop() should return the same values as it would
9+
if there were just a single stack).
10+
11+
FOLLOW UP:
12+
Implement a function popAt(int index) which performs a pop operation on a specific sub-stack.
13+
"""
14+
import copy
15+
import unittest
16+
17+
from dataclasses import dataclass
18+
from typing import Generic, TypeVar
19+
from typing import List, Optional, Generator, Iterator
20+
21+
T = TypeVar('T')
22+
23+
@dataclass
24+
class StackNode(Generic[T]):
25+
data: T
26+
next: 'Optional[StackNode[T]]'
27+
prev: 'Optional[StackNode[T]]'
28+
29+
class MyStackIterator(Generic[T], Iterator[T]):
30+
def __init__(self, top: Optional[StackNode[T]], bottom: Optional[StackNode[T]], size: int) -> None:
31+
self.forward_index = -1
32+
self.backward_index = size
33+
self._size = size
34+
self.current_node = top
35+
self.bottom_node = bottom
36+
37+
def __next__(self) -> T:
38+
self.forward_index += 1
39+
if self.forward_index == self._size or self.current_node is None:
40+
self.forward_index = -1
41+
self.current_node = None
42+
raise StopIteration
43+
n: T = self.current_node.data
44+
self.current_node = self.current_node.next
45+
return n
46+
47+
def __reversed__(self) -> T:
48+
self.backward_index -= 1
49+
if self.backward_index == -1 or self.bottom_node is None:
50+
self.backward_index = self._size
51+
self.bottom_node = None
52+
raise StopIteration
53+
n: T = self.bottom_node.data
54+
self.bottom_node = self.bottom_node.prev
55+
return n
56+
57+
class MyStack(Generic[T]):
58+
"""Stack data structure implementation.
59+
Uses LIFO (last-in first-out) ordering.
60+
The most recent item added to the stack is
61+
the first removed. Traversal is top to bottom.
62+
"""
63+
64+
def __init__(self) -> None:
65+
self.top: Optional[StackNode[T]] = None # top is a pointer to StackNode object
66+
self.bottom: Optional[StackNode[T]] = None
67+
self.size: int = 0
68+
self.current_node: Optional[StackNode[T]] = self.top
69+
return
70+
71+
def pop(self) -> T:
72+
"""
73+
Removes the top item from the stack
74+
Raises:
75+
IndexError: raised when pop is attempted on empty stack
76+
Returns:
77+
int: The data at the top of the stack
78+
"""
79+
if self.top is None:
80+
raise IndexError('Stack is Empty.')
81+
item = self.top.data
82+
self.top = self.top.next
83+
if self.size > 1 and self.top:
84+
self.top.prev = None
85+
self.size -= 1
86+
return item
87+
88+
def push(self, item: T) -> None:
89+
"""
90+
Adds an item to the top of the stack
91+
Args:
92+
item (int): data we want at the top of stack
93+
"""
94+
t = StackNode(item, None, None)
95+
t.next = self.top
96+
self.top = t
97+
if self.size == 0:
98+
self.bottom = t
99+
elif t.next:
100+
t.next.prev = t
101+
self.size += 1
102+
103+
def peek(self) -> T:
104+
"""
105+
Returns data at the top of the stack
106+
Raises:
107+
IndexError: [description]
108+
Returns:
109+
int: the value at the top of the stack
110+
"""
111+
if self.top is None:
112+
raise IndexError('Stack is Empty')
113+
return self.top.data
114+
115+
def __iter__(self) -> Iterator[T]:
116+
"""
117+
Builds a list of the current stack state.
118+
For example, given the following stack:
119+
3 -> 2 -> 1, where 3 is the top,
120+
Expect:
121+
[3, 2, 1]
122+
Returns:
123+
List[int]: list of integers
124+
"""
125+
return MyStackIterator(self.top, self.bottom, self.size)
126+
127+
def __bool__(self) -> bool:
128+
"""
129+
True is returned when the container is not empty.
130+
From https://docs.python.org/3/reference/datamodel.html#object.__bool__ :
131+
Called to implement truth value testing and the built-in operation bool();
132+
should return False or True. When this method is not defined, len() is called,
133+
if it is defined, and the object is considered true if its result is nonzero.
134+
If a class defines neither len() nor bool(), all its instances are considered true.
135+
Returns:
136+
bool: False when empty, True otherwise
137+
"""
138+
return self.size > 0
139+
140+
def __len__(self) -> int:
141+
return self.size
142+
143+
def __str__(self) -> str:
144+
if self.size == 0:
145+
return '<Empty>'
146+
values = []
147+
n = self.top
148+
while n and n.next:
149+
values.append(str(n.data))
150+
n = n.next
151+
if n:
152+
values.append(str(n.data))
153+
return '->'.join(values)
154+
155+
156+
def yield_set_of_stacks(stack_list: List[MyStack[T]]) -> Generator[T, None, None]:
157+
stack: MyStack[T]
158+
for stack in reversed(stack_list):
159+
for item in stack:
160+
yield item
161+
162+
class SetofStacks(Generic[T]):
163+
def __init__(self) -> None:
164+
self.set_of_stacks: List[MyStack[T]] = []
165+
self.stack_threshold: int = 3
166+
self.size: int = 0
167+
168+
def push(self, item: T) -> None:
169+
# threshold check
170+
if (not self.set_of_stacks) or len(self.set_of_stacks[-1]) >= self.stack_threshold:
171+
# create new stack
172+
self.set_of_stacks.append(MyStack())
173+
self.set_of_stacks[-1].push(item)
174+
self.size += 1
175+
return
176+
177+
def _pop(self) -> T:
178+
return self.set_of_stacks[-1].pop()
179+
180+
def _pop_stack(self) -> MyStack[T]:
181+
return self.set_of_stacks.pop(-1)
182+
183+
def pop(self) -> T:
184+
"""Removes element off of the current stack.
185+
We will only pop a stack if the current stack
186+
contains a single value
187+
Returns:
188+
T: popped item
189+
"""
190+
if len(self.set_of_stacks[-1]) > 1:
191+
item: T = self._pop()
192+
else:
193+
s: MyStack[T] = self._pop_stack()
194+
item = s.pop()
195+
self.size -= 1
196+
return item
197+
198+
def peek(self) -> T:
199+
return self.set_of_stacks[-1].peek()
200+
201+
def __len__(self) -> int:
202+
return self.size
203+
204+
def __iter__(self) -> Generator[T, None, None]:
205+
return yield_set_of_stacks(self.set_of_stacks)
206+
207+
208+
class TestSetofStacks(unittest.TestCase):
209+
210+
def test_setofstacks_push_and_peek(self) -> None:
211+
sos = SetofStacks()
212+
self.assertEqual(len(sos), 0)
213+
sos.push(5)
214+
self.assertEqual(len(sos), 1)
215+
self.assertEqual(sos.peek(), 5)
216+
sos.push(6)
217+
self.assertEqual(len(sos), 2)
218+
self.assertEqual(sos.peek(), 6)
219+
sos.push(7)
220+
self.assertEqual(len(sos), 3)
221+
self.assertEqual(sos.peek(), 7)
222+
223+
# with threshold of 3 (default),
224+
# verify that a new stack is created
225+
# after the next push
226+
sos.push(8)
227+
# [5->6->7->, 8->] new stack created because threshold is 3
228+
self.assertEqual(len(sos), 4)
229+
self.assertEqual(sos.peek(), 8)
230+
self.assertEqual(sos.set_of_stacks[1].peek(), 8)
231+
self.assertEqual(len(sos.set_of_stacks[1]), 1)
232+
233+
def test_setofstacks_pop(self) -> None:
234+
# pop empty stack
235+
sos = SetofStacks()
236+
with self.assertRaises(IndexError):
237+
sos.pop()
238+
sos.push(1)
239+
sos.push(2)
240+
sos.push(3)
241+
# size is 3
242+
self.assertEqual(len(sos), 3)
243+
val = sos.pop()
244+
self.assertEqual(val, 3)
245+
self.assertEqual(len(sos), 2) # size should now be 2
246+
sos.push(3)
247+
sos.push(4) # new stack created, verify that pop works as intended
248+
self.assertEqual(len(sos), 4)
249+
val = sos.pop()
250+
self.assertEqual(val, 4)
251+
self.assertEqual(len(sos), 3)
252+
253+
def test_setofstacks_pop_three_stacks(self) -> None:
254+
s = SetofStacks()
255+
# No stacks exists in set, yet
256+
self.assertEqual(len(s.set_of_stacks), 0)
257+
s.push(1)
258+
s.push(2)
259+
s.push(3)
260+
self.assertEqual(len(s.set_of_stacks), 1)
261+
# threshold met, should be two after next push
262+
s.push(4)
263+
# should be 2 stacks now
264+
self.assertEqual(len(s.set_of_stacks), 2)
265+
s.push(5)
266+
s.push(6)
267+
self.assertEqual(len(s.set_of_stacks), 2)
268+
s.push(7)
269+
self.assertEqual(len(s.set_of_stacks), 3)
270+
s.push(8)
271+
s.push(9)
272+
self.assertEqual(len(s.set_of_stacks), 3)
273+
s.push(10)
274+
# should be four stacks now
275+
self.assertEqual(len(s.set_of_stacks), 4)
276+
self.assertEqual(list(s), [10, 9, 8 , 7, 6, 5, 4, 3, 2, 1])
277+
self.assertEqual(len(s), 10)
278+
val = s.pop()
279+
self.assertEqual(len(s.set_of_stacks), 3)
280+
self.assertEqual(val, 10)
281+
self.assertEqual(len(s), 9)
282+
self.assertEqual(list(s), [9, 8 , 7, 6, 5, 4, 3, 2, 1])
283+
val = s.pop()
284+
# length of stacks should still be 3
285+
self.assertEqual(len(s.set_of_stacks), 3)
286+
self.assertEqual(val, 9)
287+
self.assertEqual(len(s), 8)
288+
self.assertEqual(list(s), [8, 7, 6, 5, 4, 3, 2, 1])
289+
val = s.pop()
290+
# after this pop, should still have 3
291+
self.assertEqual(len(s.set_of_stacks), 3)
292+
self.assertEqual(val, 8)
293+
self.assertEqual(len(s), 7)
294+
self.assertEqual(list(s), [7, 6, 5, 4, 3, 2, 1])
295+
val = s.pop()
296+
self.assertEqual(len(s.set_of_stacks), 2)
297+
self.assertEqual(val, 7)
298+
self.assertEqual(len(s), 6)
299+
self.assertEqual(list(s), [6, 5, 4, 3, 2, 1])
300+
val = s.pop()
301+
self.assertEqual(len(s.set_of_stacks), 2)
302+
self.assertEqual(val, 6)
303+
self.assertEqual(len(s), 5)
304+
self.assertEqual(list(s), [5, 4, 3, 2, 1])
305+
val = s.pop()
306+
self.assertEqual(len(s.set_of_stacks), 2)
307+
self.assertEqual(val, 5)
308+
self.assertEqual(len(s), 4)
309+
self.assertEqual(list(s), [4, 3, 2, 1])
310+
val = s.pop()
311+
self.assertEqual(len(s.set_of_stacks), 1)
312+
self.assertEqual(val, 4)
313+
self.assertEqual(len(s), 3)
314+
self.assertEqual(list(s), [3, 2, 1])
315+
val = s.pop()
316+
self.assertEqual(len(s.set_of_stacks), 1)
317+
self.assertEqual(val, 3)
318+
self.assertEqual(len(s), 2)
319+
self.assertEqual(list(s), [2, 1])
320+
321+
322+
class TestMyStack(unittest.TestCase):
323+
def test_stack_push(self) -> None:
324+
s = MyStack()
325+
self.assertEqual(len(s), 0)
326+
self.assertEqual(s.top, None)
327+
s.push(2)
328+
self.assertEqual(len(s), 1)
329+
self.assertEqual(s.top.data, 2)
330+
self.assertEqual(s.top.next, None)
331+
s.push(3)
332+
self.assertEqual(len(s), 2)
333+
self.assertEqual(s.top.data, 3)
334+
self.assertEqual(s.top.next.data, 2)
335+
s.push(4)
336+
self.assertEqual(len(s), 3)
337+
self.assertEqual(s.top.data, 4)
338+
self.assertEqual(s.top.next.data, 3)
339+
l = list(s)
340+
self.assertEqual(l, [4, 3, 2])
341+
342+
def test_stack_peek(self) -> None:
343+
s = MyStack()
344+
with self.assertRaises(IndexError):
345+
s.peek()
346+
s.push(1)
347+
s.push(2)
348+
s.push(99)
349+
top_val = s.peek()
350+
self.assertEqual(top_val, 99)
351+
352+
def test_stack_pop(self) -> None:
353+
# first case, attempt to pop an empty stack
354+
s = MyStack()
355+
with self.assertRaises(IndexError):
356+
s.pop()
357+
s.push(1)
358+
s.push(2)
359+
s.push(3)
360+
# size is 3
361+
self.assertEqual(list(s), [3, 2, 1])
362+
val = s.pop()
363+
self.assertEqual(val, 3)
364+
self.assertEqual(len(s), 2) # size should now be 2
365+
self.assertEqual(list(s), [2, 1])
366+
367+
def test__bool__(self) -> None:
368+
s = MyStack()
369+
self.assertFalse(s)
370+
s.push(3)
371+
self.assertTrue(s)
372+
373+
374+
if __name__ == '__main__':
375+
unittest.main()

0 commit comments

Comments
 (0)