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