Skip to content

Commit bf9a928

Browse files
committed
Add: Queue using Stacks in Python- Implements a FIFO queue using two stacks- Time Complexity: Enqueue O(1), Dequeue O(1) amortized- Space Complexity: O(n)
1 parent 63f4695 commit bf9a928

File tree

1 file changed

+155
-0
lines changed

1 file changed

+155
-0
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# Python/data_structures/queue/queue_using_stacks.py
2+
3+
"""
4+
Data Structure Description:
5+
This implements a Queue (FIFO - First-In, First-Out) using two Stacks
6+
(LIFO - Last-In, First-Out). One stack (`_in_stack`) is used for enqueue
7+
operations, and the other (`_out_stack`) is used for dequeue and peek
8+
operations. Elements are moved from `_in_stack` to `_out_stack` only when
9+
`_out_stack` is empty, ensuring the FIFO order is maintained efficiently
10+
on average (amortized O(1) for dequeue).
11+
"""
12+
13+
# Time Complexity:
14+
# - Enqueue: O(1) - Simply push onto the in_stack.
15+
# - Dequeue: O(1) amortized. In the worst case (when out_stack is empty
16+
# and in_stack has n elements), it takes O(n) to transfer elements.
17+
# However, each element is moved only once, so over n operations, the
18+
# average cost is O(1).
19+
# - Peek: O(1) amortized - Same reasoning as dequeue.
20+
# - is_empty: O(1)
21+
# - size: O(1)
22+
# Space Complexity: O(n)
23+
# - Where n is the number of elements stored across both stacks.
24+
25+
class QueueUsingStacks:
26+
"""Implements a FIFO queue using two LIFO stacks."""
27+
28+
def __init__(self):
29+
"""Initializes the two stacks."""
30+
self._in_stack = [] # Used for enqueue
31+
self._out_stack = [] # Used for dequeue/peek
32+
33+
def enqueue(self, item):
34+
"""Adds an item to the rear of the queue.
35+
>>> q = QueueUsingStacks()
36+
>>> q.enqueue(1)
37+
>>> q.enqueue(2)
38+
>>> print(q._in_stack)
39+
[1, 2]
40+
"""
41+
self._in_stack.append(item)
42+
43+
def _transfer_elements(self):
44+
"""Transfers elements from in_stack to out_stack if out_stack is empty."""
45+
if not self._out_stack:
46+
while self._in_stack:
47+
self._out_stack.append(self._in_stack.pop())
48+
49+
def dequeue(self):
50+
"""Removes and returns the item from the front of the queue.
51+
Raises IndexError if the queue is empty.
52+
>>> q = QueueUsingStacks()
53+
>>> q.enqueue(1)
54+
>>> q.enqueue(2)
55+
>>> q.dequeue() # Transfers 1, 2 to out_stack [2, 1], pops 1
56+
1
57+
>>> q.enqueue(3)
58+
>>> q.dequeue() # Pops 2 from out_stack
59+
2
60+
>>> q.dequeue() # Transfers 3 to out_stack [3], pops 3
61+
3
62+
>>> q.dequeue()
63+
Traceback (most recent call last):
64+
...
65+
IndexError: dequeue from empty queue
66+
"""
67+
self._transfer_elements()
68+
if not self._out_stack:
69+
raise IndexError("dequeue from empty queue")
70+
return self._out_stack.pop()
71+
72+
def peek(self):
73+
"""Returns the item at the front of the queue without removing it.
74+
Raises IndexError if the queue is empty.
75+
>>> q = QueueUsingStacks()
76+
>>> q.enqueue(1)
77+
>>> q.enqueue(2)
78+
>>> q.peek() # Transfers 1, 2 to out_stack [2, 1], returns 1
79+
1
80+
>>> q.dequeue()
81+
1
82+
>>> q.peek() # Returns 2 from out_stack
83+
2
84+
>>> q.dequeue()
85+
2
86+
>>> q.peek()
87+
Traceback (most recent call last):
88+
...
89+
IndexError: peek from empty queue
90+
"""
91+
self._transfer_elements()
92+
if not self._out_stack:
93+
raise IndexError("peek from empty queue")
94+
return self._out_stack[-1] # Peek at the top of out_stack
95+
96+
def is_empty(self):
97+
"""Returns True if the queue is empty, False otherwise.
98+
>>> q = QueueUsingStacks()
99+
>>> q.is_empty()
100+
True
101+
>>> q.enqueue(1)
102+
>>> q.is_empty()
103+
False
104+
"""
105+
return not self._in_stack and not self._out_stack
106+
107+
def size(self):
108+
"""Returns the total number of items in the queue.
109+
>>> q = QueueUsingStacks()
110+
>>> q.size()
111+
0
112+
>>> q.enqueue(1)
113+
>>> q.enqueue(2)
114+
>>> q.size()
115+
2
116+
>>> q.dequeue()
117+
1
118+
>>> q.size()
119+
1
120+
"""
121+
return len(self._in_stack) + len(self._out_stack)
122+
123+
def __len__(self):
124+
"""Allows using len(q)."""
125+
return self.size()
126+
127+
def __str__(self):
128+
"""String representation showing logical FIFO order (may be slow)."""
129+
# Note: This is inefficient for visualization but shows the logical order
130+
elements = list(reversed(self._out_stack)) + list(self._in_stack)
131+
return f"QueueUsingStacks({elements})"
132+
133+
def __repr__(self):
134+
"""Representation showing internal stack states."""
135+
return f"QueueUsingStacks(in={self._in_stack}, out={self._out_stack})"
136+
137+
if __name__ == "__main__":
138+
import doctest
139+
doctest.testmod()
140+
141+
q = QueueUsingStacks()
142+
print("Is empty?", q.is_empty())
143+
q.enqueue('A')
144+
q.enqueue('B')
145+
print(q)
146+
print("Peek:", q.peek())
147+
print("Dequeue:", q.dequeue())
148+
print(q)
149+
q.enqueue('C')
150+
print(q)
151+
print("Size:", len(q))
152+
print("Dequeue:", q.dequeue())
153+
print("Dequeue:", q.dequeue())
154+
print("Is empty?", q.is_empty())
155+
# print("Dequeue:", q.dequeue()) # Raises IndexError

0 commit comments

Comments
 (0)