|
| 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