Chapter 6 Exercises
Chapter 6 Exercises
General Notes:
• Many of these exercises rely on the Stack and Queue classes, which are implemented in R6-3 and
R6-11, respectively
• Some of the exercises have test cases that are designed to trigger exceptions to indicate they
were successful
Reinforcement
In [1]:
#-----------R6-1---------------------
"""
Return Values in the Stack
- [5]
- [5,3]
3 [5]
- [5,2]
- [5,2,8]
8 [5,2]
2 [5]
- [5,9]
- [5,9,1]
1 [5,9]
- [5,9,7]
- [5,9,7,6]
6 [5,9,7]
7 [5,9]
- [5,9,4]
4 [5,9]
9 [5]
Note, we have 9 pushes and 8 pops, so wehave one value left in the stack as
expected
"""
Out[1]:
'\nReturn Values in the Stack\n- [5]\n- [5,3]\n3 [5]
\n- [5,2]\n- [5,2,8]\n8 [5,2]\n2 [5]\n-
[5,9]\n- [5,9,1]\n1 [5,9]\n- [5,9,7]\n- [5,9,7,
6]\n6 [5,9,7]\n7 [5,9]\n- [5,9,4]\n4 [5,9]\n9
[5]\n\n\nNote, we have 9 pushes and 8 pops, so wehave one value left in the
stack as expected\n\n'
In [2]:
#-----------R6-2------------------
"""
Note: top doens't add or remove any element, so we can ignore those
"""
Out[2]:
"\nNote: top doens't add or remove any element, so we can ignore those\n\nI
f 3 pop operations failed, we only effectively had 7 complete pops\n\nThere
fore, we should have 25-7 = 18 elements in the stack\n\n"
In [3]:
#----------R6-3---------------------
class Empty(Exception):
pass
class Stack():
def __init__(self):
self._data = []
def __len__(self):
return len(self._data)
def is_empty(self):
return len(self._data) == 0
def top(self):
return self._data[-1]
def pop(self):
if self.is_empty():
raise Empty('List is empty')
return self._data.pop()
def full_pop(self):
ans = []
while not self.is_empty():
ans.append(self.pop())
return ans
try: S.pop()
except Exception as e: print (e)
for i in range(20):
S.push(i)
Although we pop them in order,they get appended to the output list in the
reverse order that they're
popper normally. We can rectify this by letting the popped value get
extended by the output array;
however, this will cause the algorithm to run in O(n^2) since the extension
will take O(n) time
instead of O(1) for a single extension. Since we know len, we could also
just initialize an
array and fill in the values as they're popped (using a counter)
"""
S = RStack()
for i in range(20):S.push(i)
print('Option 1:', S.full_pop(in_order = True))
S2 = R2Stack()
for i in range(20):S2.push(i)
print('Option 2:', S2.full_pop())
Option 1: [19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2,
1, 0]
Option 2: [19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2,
1, 0]
In [5]:
#----------R6-5-------------
"""
Note, this relies on the stack from R6-3
"""
def reverse_list(S):
temp = Stack()
for e in S:
temp.push(e)
for i in range(len(S)):
S[i] = temp.pop()
lists = [[1,2,3,4,5,6],
[3,2,5,6,7,8,9,5],
[9,8,7,6,5,4,3,2,1]]
for l in lists:
print ('Before: ', l)
reverse_list(l)
print('After: ', l)
print()
Before: [1, 2, 3, 4, 5, 6]
After: [6, 5, 4, 3, 2, 1]
Before: [3, 2, 5, 6, 7, 8, 9, 5]
After: [5, 9, 8, 7, 6, 5, 2, 3]
Before: [9, 8, 7, 6, 5, 4, 3, 2, 1]
After: [1, 2, 3, 4, 5, 6, 7, 8, 9]
In [6]:
#------------R6-6---------------------
"""
Note, this relies on the Stack class form R6-3
"""
"""
Note 2: This is a sloppy implementation of this solution!
I'm not entirely sure what this problem is asking since the solution
presented
in Code Fragment 6.4 would work here as well. Instead, I think it might be
checking
that the order is (sequence that produces a num) operator (sequence that
produces a num)
The recursive aspect would involve checking that the values within a set of
brackets is also an arithmetic expression,
with the base case being that you don't find any
Add to stack until you get to a close bracket, then perform a 'check pop'
until you get to the previous open bracket.
The result should be a number
Which means that you start by either finding a number or an (. If you find
an (, recurse to see if it collapses into
a number. If so, then look for an operation and the next number, and see
if those collapse as well
Every time you see the num op num patterns you collapse into one number
"""
class AlgoStack(Stack):
def __repr__(self):
return str(self._data)
if char in self.CLOSE:
if not self.pop_until_open(): return False
else: self.push(char)
return True
exps = ['(5+4)*(3/(2+3))',
'(5+4+)*(3/(2+3))',
'(5+4))*(3/(2+3))',
'5+(5+4)*(3/(2+3))']
for exp in exps:
ss = AlgoStack()
print(exp, ss.check_expression(exp))
(5+4)*(3/(2+3)) True
(5+4+)*(3/(2+3)) False
(5+4))*(3/(2+3)) False
5+(5+4)*(3/(2+3)) True
In [7]:
#-------------R6-7---------------------
"""
Return Values in the Stack
- [5]
- [5,3]
5 [3]
- [3,2]
- [3,2,8]
3 [2,8]
2 [8]
- [8,9]
- [8,9,1]
8 [9,1]
- [9,1,7]
- [9,1,7,6]
9 [1,7,6]
1 [7,6]
- [7,6,4]
7 [6,4]
6 [4]
"""
Out[7]:
'\nReturn Values in the Stack\n- [5]\n- [5,3]\n5 [3]
\n- [3,2]\n- [3,2,8]\n3 [2,8]\n2 [8]\n-
[8,9]\n- [8,9,1]\n8 [9,1]\n- [9,1,7]\n- [9,1,7,
6]\n9 [1,7,6]\n1 [7,6]\n- [7,6,4]\n7 [6,4]\n6
[4]\n\n'
In [8]:
#----------R6-8--------------
"""
Like before, a dequeue operation that fails doesn't remove anything from
the queue, so there are essentially 10 dequeues
"""
Out[8]:
"\nLike before, a dequeue operation that fails doesn't remove anything from
the queue, so there are essentially 10 dequeues\n\nFirst operations don't c
hange the queue at all\n\nTherefore we have 32-10 = 22 (the size of the que
ue is 22 at that point)\n\n"
In [9]:
#-------------R6-9-----------------
"""
Since it was initially an empty queue, we will assume the front value was
initially 0 (althogh this is
not necessarily the case since you can empty the queue when front is a
value other than 1)
self._front only increments when a dequeue takes place so the final value
would be 10 ahead of its initial value,
or self._front == 10 is self._front was initially 0 as assumed. More
generally, it's (10+initial) % 30
"""
Out[9]:
"\nSince it was initially an empty queue, we will assume the front value wa
s initially 0 (althogh this is\nnot necessarily the case since you can empt
y the queue when front is a value other than 1)\n\nself._front only increme
nts when a dequeue takes place so the final value would be 10 ahead of its
initial value, \nor self._front == 10 is self._front was initially 0 as ass
umed. More generally, it's (10+initial) % 30\n\n"
In [10]:
#------------R6-10------------------
"""
This method will copy the data in exactly, but now there will be a huge gap
between the middle of the data
filled with None and the next value (which is situated at ._data[0])
would become:
F
5,6,7,1,2,3,4,N,N,N,N,N,N,N
In contract, the walk assures that the data will stay both in order and in
direct sequence, producing:
F
1,2,3,4,5,6,7,N,N,N,N,N,N,N
"""
Out[10]:
'\nThis method will copy the data in exactly, but now there will be a huge
gap between the middle of the data\nfilled with None and the next value (wh
ich is situated at ._data[0])\n\nex. a queue with 1,2,3,4,5,6,7 with front
= 3:\n F\n5,6,7,1,2,3,4\n\nwould become:\n F\n5,6,7,1,2,3,4,N,N,N
,N,N,N,N\n\nthe next insertion would be at front + self._size, which would
be:\n F\n5,6,7,1,2,3,4,N,N,N,8,N,N,N and so on, which results in the f
ragmentation of the data. Successive dequeues would \nresult in 1,2,3,4,N,
N,N,....., N,N instead of the desired 1,2,3,4,5,6,7, is_empty==True\n\n\nIn
contract, the walk assures that the data will stay both in order and in dir
ect sequence, producing:\n\nF\n1,2,3,4,5,6,7,N,N,N,N,N,N,N\n\n'
In [11]:
#--------------R6-11---------------------------
"""
In our ADT, we want the following behaviours:
enqueue
dequeue
first
is_empty
len
"""
import collections
class Queue():
def __init__(self):
self._data = collections.deque()
self._size = 0
def __len__(self):
return self._size
def first(self):
return self._data[0]
def is_empty(self):
return self._size == 0
def dequeue(self):
if self.is_empty():
raise ValueError('Queue is empty')
else:
ans = self._data.popleft()
self._size -= 1
return ans
dq = Queue()
for i in range(10):
dq.enqueue(i)
"""
Out[12]:
"\n\nReturn Values in the Stack\n- [4]\n- [4, 8]\n-
[4, 8, 9]\n- [5, 4, 8, 9]\n9 [5, 4, 8, 9] (the back operation
- I'm assuming they meant last() based on their implementation)\n5 [
4, 8, 9]\n9 [4,8]\n- [4,8,7]\n4 [4,8,7]\n7 [4,8
,7]\n- [4,8,7,6]\n4 [8,7,6]\n8 [7,6]\n\n"
In [13]:
#------------R6-13----------------
"""
D is a deque, which means that it can accept input at either end
Q is a queue, which follows the FIFO approach
Note, you can check whether each step is correct by commenting out the
subsequent lines and checking against output at that point
"""
import collections
#Setup
D = collections.deque()
Q = Queue()
for i in range(1, 9, 1):
D.append(i)
for i in range(3):
D.append(Q.dequeue()) #[6,7,8,1,2,3], [4,5]
for i in range(2):
D.appendleft(Q.dequeue()) #[5,4,6,7,8,1,2,3], []
for i in range(3):
Q.enqueue(D.pop()) #[5,4,6,7,8], [3,2,1]
for i in range(3):
D.appendleft(Q.dequeue()) #[1,2,3,5,4,6,7,8]
rearrange_using_queue(D, Q)
print('Values of Q:')
while not Q.is_empty():
print(Q.dequeue())
print('Values of D')
while len(D) != 0:
print(D.popleft())
Values of Q:
Values of D
1
2
3
5
4
6
7
8
In [14]:
#---------------R6-14----------------------
import collections
S = Stack()
D = collections.deque()
for i in range(1,9,1):
D.append(i)
for _ in range(5):
D.appendleft(S.pop()) #[1,2,3,5,4,6,7,8], []
rearrange_using_stack(D, S)
print('Values of S')
while not S.is_empty():
print(S.pop())
print('Values of D:')
while len(D) != 0:
print(D.popleft())
Values of S
Values of D:
1
2
3
5
4
6
7
8
Creativity
In [15]:
#-----------------C6-15------------------
"""
This problem relies on the fact that there are two situations:
The first is that the largest value is one of the ones that you see in the
comparison.
If that is true, you will have a 100% change of getting it right and there
is a 2/3 chance
that this will be the situation you face
On the other hand, if the largest value is not in the comparison, you will
have a 0%
chance of getting it right. This situation will happen 1/3 of the time
"""
import random
#note, could also do x = max(S.pop(), x), but I'm not sure if max is
allowed
num_trials = 1000
total = 0
numbers = [1,2,3]
S = Stack()
for _ in range(num_trials):
if max_picker(S, numbers) == max(numbers): total += 1
class Full(Exception):
pass
class ArrayStack():
def __init__(self, maxlen = None):
self._data = []
self._maxlen = maxlen
def __len__(self):
return len(self._data)
def is_empty(self):
return len(self._data) == 0
def top(self):
if self.is_empty():
raise Empty('Stack is empty')
return self._data[-1]
def pop(self):
if self.is_empty():
raise Empty('Stack is empty')
return self._data.pop()
astack = ArrayStack(10)
for i in range(20):
try:
astack.push(i)
print(astack.top())
except Full as e:
print(e)
0
1
2
3
4
5
6
7
8
9
The array is full
The array is full
The array is full
The array is full
The array is full
The array is full
The array is full
The array is full
The array is full
The array is full
In [17]:
#------------------C6-17-------------------
"""
Note, for this one we now have to manage the number of elements we insert
"""
class Empty(Exception):
pass
class Full(Exception):
pass
class ArrayStackPrealloc():
def __init__(self, maxlen = 20):
self._capacity = maxlen
self._data = [None]*maxlen
self._n = 0
def __len__(self):
return self._n
def is_empty(self):
return len(self) == 0
def pop(self):
if self.is_empty():
raise Empty('Stack is empty')
ans = self._data[self._n - 1]
self._data[self._n - 1] = None
self._n -= 1
return ans
def top(self):
if self.is_empty():
raise Empty('Stack is empty')
return self._data[self._n-1]
apstack = ArrayStackPrealloc(10)
for i in range(20):
try:
apstack.push(i)
print(apstack.top())
except Full as e:
print(e)
0
1
2
3
4
5
6
7
8
9
The stack is full
The stack is full
The stack is full
The stack is full
The stack is full
The stack is full
The stack is full
The stack is full
The stack is full
The stack is full
In [18]:
#----------C6-18----------------
"""
Note, each time you transfer the contents from one stack to another, they
are reversed
Therefore, you need to use two stacks to place it back into the original in
reverse order
"""
for i in range(10):
s.push(i)
s_orig.push(i)
def reverse_stack(S):
s1 = Stack()
s2 = Stack()
reverse_stack(s)
"""
def is_matched_html(raw):
S = ArrayStack() #Need Array Stack from C6-16
j = raw.find('<')
while j != -1: #j = -1 means that you didn't find one
k = raw.find('>', j+1)
if k == -1:
return False #Your tag didn't close
return True
html = """
<body>
<center>
<h1> The Little Boat </h1>
</center attr = 'Test'>
<p attribute1="value1" attribute2="value2"> The storm tossed the little
boat like a cheap sneaker in an
old washing machine. The three
drunken fishermen were used to
such treatment, of course, but
not the tree salesman, who even as
a stowaway now felt that he
had overpaid for the voyage. </p>
<ol>
<li attribute1="value1" attribute2="value2"> Will the salesman die? </li>
<li> What color is the boat? </li>
<li> And what about Naomi? </li>
</ol>
</body>
"""
is_matched_html(html)
Out[19]:
True
In [20]:
#------------C6-20-------------
"""
Note: This one was very challenging for me to figure out, but the solution
was to treat each 'call' to a recursive
function as a push on the stack, and then pop them to actually execute the
call. Once it was framed like that, the
problem was simple
The hint recommends that we push one value onto the stack so that we are
left with n-1 numbers
If we start with a complete list, and remove one from it, our list will
have n-1 numbers.
With recursion, we sent in both the current list and the remainder, so
let's do that here as well.
Note that you have to copy the lists, or the mutability of the lists will
ruin the process (try removing it to see!)
"""
def find_permutations_stack(n):
nums = {x for x in range(1, n+1, 1)}
S = Stack() #Need Stack from R6-3
find_permutations_stack(5)
[5, 4, 3, 2, 1]
[5, 4, 3, 1, 2]
[5, 4, 2, 3, 1]
[5, 4, 2, 1, 3]
[5, 4, 1, 3, 2]
[5, 4, 1, 2, 3]
[5, 3, 4, 2, 1]
[5, 3, 4, 1, 2]
[5, 3, 2, 4, 1]
[5, 3, 2, 1, 4]
[5, 3, 1, 4, 2]
[5, 3, 1, 2, 4]
[5, 2, 4, 3, 1]
[5, 2, 4, 1, 3]
[5, 2, 3, 4, 1]
[5, 2, 3, 1, 4]
[5, 2, 1, 4, 3]
[5, 2, 1, 3, 4]
[5, 1, 4, 3, 2]
[5, 1, 4, 2, 3]
[5, 1, 3, 4, 2]
[5, 1, 3, 2, 4]
[5, 1, 2, 4, 3]
[5, 1, 2, 3, 4]
[4, 5, 3, 2, 1]
[4, 5, 3, 1, 2]
[4, 5, 2, 3, 1]
[4, 5, 2, 1, 3]
[4, 5, 1, 3, 2]
[4, 5, 1, 2, 3]
[4, 3, 5, 2, 1]
[4, 3, 5, 1, 2]
[4, 3, 2, 5, 1]
[4, 3, 2, 1, 5]
[4, 3, 1, 5, 2]
[4, 3, 1, 2, 5]
[4, 2, 5, 3, 1]
[4, 2, 5, 1, 3]
[4, 2, 3, 5, 1]
[4, 2, 3, 1, 5]
[4, 2, 1, 5, 3]
[4, 2, 1, 3, 5]
[4, 1, 5, 3, 2]
[4, 1, 5, 2, 3]
[4, 1, 3, 5, 2]
[4, 1, 3, 2, 5]
[4, 1, 2, 5, 3]
[4, 1, 2, 3, 5]
[3, 5, 4, 2, 1]
[3, 5, 4, 1, 2]
[3, 5, 2, 4, 1]
[3, 5, 2, 1, 4]
[3, 5, 1, 4, 2]
[3, 5, 1, 2, 4]
[3, 4, 5, 2, 1]
[3, 4, 5, 1, 2]
[3, 4, 2, 5, 1]
[3, 4, 2, 1, 5]
[3, 4, 1, 5, 2]
[3, 4, 1, 2, 5]
[3, 2, 5, 4, 1]
[3, 2, 5, 1, 4]
[3, 2, 4, 5, 1]
[3, 2, 4, 1, 5]
[3, 2, 1, 5, 4]
[3, 2, 1, 4, 5]
[3, 1, 5, 4, 2]
[3, 1, 5, 2, 4]
[3, 1, 4, 5, 2]
[3, 1, 4, 2, 5]
[3, 1, 2, 5, 4]
[3, 1, 2, 4, 5]
[2, 5, 4, 3, 1]
[2, 5, 4, 1, 3]
[2, 5, 3, 4, 1]
[2, 5, 3, 1, 4]
[2, 5, 1, 4, 3]
[2, 5, 1, 3, 4]
[2, 4, 5, 3, 1]
[2, 4, 5, 1, 3]
[2, 4, 3, 5, 1]
[2, 4, 3, 1, 5]
[2, 4, 1, 5, 3]
[2, 4, 1, 3, 5]
[2, 3, 5, 4, 1]
[2, 3, 5, 1, 4]
[2, 3, 4, 5, 1]
[2, 3, 4, 1, 5]
[2, 3, 1, 5, 4]
[2, 3, 1, 4, 5]
[2, 1, 5, 4, 3]
[2, 1, 5, 3, 4]
[2, 1, 4, 5, 3]
[2, 1, 4, 3, 5]
[2, 1, 3, 5, 4]
[2, 1, 3, 4, 5]
[1, 5, 4, 3, 2]
[1, 5, 4, 2, 3]
[1, 5, 3, 4, 2]
[1, 5, 3, 2, 4]
[1, 5, 2, 4, 3]
[1, 5, 2, 3, 4]
[1, 4, 5, 3, 2]
[1, 4, 5, 2, 3]
[1, 4, 3, 5, 2]
[1, 4, 3, 2, 5]
[1, 4, 2, 5, 3]
[1, 4, 2, 3, 5]
[1, 3, 5, 4, 2]
[1, 3, 5, 2, 4]
[1, 3, 4, 5, 2]
[1, 3, 4, 2, 5]
[1, 3, 2, 5, 4]
[1, 3, 2, 4, 5]
[1, 2, 5, 4, 3]
[1, 2, 5, 3, 4]
[1, 2, 4, 5, 3]
[1, 2, 4, 3, 5]
[1, 2, 3, 5, 4]
[1, 2, 3, 4, 5]
In [21]:
#---------C6-21---------------------
"""
Note, we solved a similar problem in section 4 (Problem C4-15)
The trick was to always both add an UNK dummy variable and the new value to
the working set
That way you had a space to keep growing those sets that didn't include the
current element
Here, we will use the queue to go through all the current values and place
the results on the stack
If we enqueued them right away, we would never reach the Q.is_empty()
condition. The stack is therefore
just temporary storage!
After that, we can repopulate the queue using the stack for the next
operation
Note that we have to make copies of the subsets so that they won't be
further modified later! Try it withough
.copy to see the difference
"""
UNK = chr(1000)
def subsets_without_recursion(numbers):
S = Stack() #Need Stack from R6-3
Q = Queue() #Need Queue from R6-11
for element in numbers:
if Q.is_empty():
Q.enqueue(set([element]))
Q.enqueue(set([UNK]))
else:
#Process all the elements of the Queue
while not Q.is_empty():
val = Q.dequeue()
new_set_1 = val.copy()
new_set_1.add(element)
S.push(new_set_1)
new_set_2 = val.copy()
new_set_2.add(UNK)
S.push(new_set_2)
#Once you are dont the loop, the Queue should be filled with sets
while not Q.is_empty():
output = Q.dequeue()
print ('{', str([x for x in output if x != UNK])[1:-1], '}')
subsets_without_recursion({1,2,3,4,5})
{ 1, 2, 4 }
{ 1, 2, 4, 5 }
{ 1, 2 }
{ 1, 2, 5 }
{ 1, 2, 3, 4 }
{ 1, 2, 3, 4, 5 }
{ 1, 2, 3 }
{ 1, 2, 3, 5 }
{ 1, 4 }
{ 1, 5, 4 }
{ 1 }
{ 1, 5 }
{ 1, 3, 4 }
{ 1, 3, 4, 5 }
{ 1, 3 }
{ 1, 3, 5 }
{ 2, 4 }
{ 2, 5, 4 }
{ 2 }
{ 2, 5 }
{ 2, 3, 4 }
{ 2, 3, 4, 5 }
{ 2, 3 }
{ 2, 3, 5 }
{ 4 }
{ 5, 4 }
{ }
{ 5 }
{ 3, 4 }
{ 5, 3, 4 }
{ 3 }
{ 3, 5 }
In [22]:
#-------------C6-22--------------------
"""
Note, this problem can be solved using a stack. If you push numbers on a
stack, whenever you encounter
and operator, you use it to process the previous two numbers and the put
the result on the stack
Note that you should remember that the first number you pop is actually
pexp2, so you have to be careful!!
For simplicity, we will assume the inputs come in as a list. That was we
don't have to convert strings to numbers
(ex. 1.234+453/445 is easier to process as [1.234, +, 452, / 445] for
normal notation)
"""
import operator
class Empty(Exception):
pass
class AdditionalValues(Exception):
pass
class prefix_notation_assessment():
OPERATORS = {'+': operator.add,
'-': operator.sub,
'*': operator.mul,
'/': operator.truediv,
'^': operator.pow,
'**': operator.pow} #Power can be done in two ways
def __init__(self):
self._S = Stack() #Relies on Stack from R6-3
def _is_empty(self):
return self._S.is_empty()
def _pop(self):
if self._S.is_empty():
raise Empty('Operator without value')
else:
return self._S.pop()
#Once you are done, there should only be one value left!
result = self._pop()
if self._is_empty(): return result
else: raise AdditionalValues('Invalid Expression: you have entered
more values than the operator processed')
pf_assess = prefix_notation_assessment()
exps = [[5,2,'+',8,3,'-','*',4,'/'],
[5,2,3,'+',8,3,'-','*',4,'/', '**'],
[5,2, 3,'+',8,3,'-','*',4,'/'],
[5,2,'+',8,3,'-','*',4,'/', '*']
]
For this problem, the lists are not reversed, so we know that we need to
use an even number of stack
transfers (ex. A->B->A) to preserve the order. In contrast, we use an odd
number of stack transfers
(ex. A->B->C->A) to reverse the order
The trick to this problem is knowing how many elements are in each set
(that is the reason R has values)
We could also use len(S), but I'm not sure if that's allowed
"""
"""
#Step 1: Transfer
len_S = 0 #Alternatively,just use len(S) to get the length
while not S.is_empty():
R.push(S.pop())
len_S += 1
transfer_below(R, S, T)
print('Values of R:')
while not R.is_empty():
print (R.pop())
print('Values of S:')
while not S.is_empty():
print (S.pop())
print('Values of T:')
while not T.is_empty():
print (T.pop())
Values of R:
3
2
1
Values of S:
5
4
9
8
7
6
Values of T:
In [24]:
#----------------C6-24------------------------
"""
The hint for this problem gives it away. If your queue is N long, you can
get the last element
to the front of the queue by performing n-1 dequeue, enqueue operations
To push, you add a value to the end of the list and then rotate it to the
front
class StackUsingQueue():
def __init__(self):
self._data = Queue()
self._n = 0 #number of elements
def is_empty(self):
return self._data.is_empty()
def pop(self):
if self.is_empty():
raise Empty('Cannot pop from an empty stack')
ans = self._data.dequeue()
self._n -= 1
return ans
def top(self):
return self._data.first()
def __len__(self):
return self._n
s = StackUsingQueue()
for i in range(10):
s.push(i)
"""
With this implementation:
"""
9 9
8 8
7 7
6 6
5 5
4 4
3 3
2 2
1 1
0 0
Out[24]:
"\nWith this implementation:\n\ntop() is O(1) amortized\npop() is O(1) am
ortized\npush() is O(n)\n\nSo actually, it's really only the pushes that tr
uly suffer\n\n\n"
In [25]:
#------------C6-25------------------
"""
The queue ADT requires that you enqueue and dequeue
We saw in exercise C6-23 that we can put a new value below previous ones by
using an intermediate stack
The trick to this one is that we need to do it in O(1) time, which can be
accomplished if we know that
an odd number of transfers reverses a Stack. For example, if I push in the
order, 1, 2, 3 and then
transfer that entire stack to a new stack, the order will be 3, 2, 1 and 1
will be the first to pop
"""
class Empty(Exception):
pass
class QueueUsingStacks():
def __init__(self):
self._Dstack, self._nd = Stack(), 0 #Dequeuing Stack and
num_elements
self._Estack, self._ne = Stack(), 0 #Enqueuing Stack and
num_elements
def is_empty(self):
return (self._nd + self._ne) == 0
def _stack_transfer(self):
while self._ne >0:
self._Dstack.push(self._Estack.pop())
self._ne -= 1
self._nd += 1
def dequeue(self):
#If the dequeue stack is empty, pop all values over from the
enqueue stack
if self._nd == 0:
if self._ne == 0: raise Empty('Your Queue is empty!')
self._stack_transfer()
#Once the Dequeue stack has been repopulated, pop the top value
ans = self._Dstack.pop()
self._nd -= 1
return ans
def first(self):
if self._nd == 0:
if self._ne == 0: raise Empty('Your Queue is empty!')
self._stack_transfer()
return self._Dstack.top()
Q= QueueUsingStacks()
for i in range(10):
Q.enqueue(i)
"""
With this implementation:
each dequeue takes O(1) if there is no stack transfer since pop is O(1)
However, in the event of a stack transfer, it can take O(n) in the worst
case.
As each element can only move to the dequeue stack once, if we add one
extra cyber-dollar for every
enqueue, we will spend them exactly to move all those elements to the
dequeue stack, which means
that we've amortized is to O(c), or O(1)
In other words, O(n) dequeues will involve O(n) pops and a total of O(n)
pop, push operations to
change stacks. As a result, each individual dequeue is amortized to O(1)
A similar proof exists for first()
"""
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
Out[25]:
"\nWith this implementation:\n\neach enqueue takes O(1) since a push takes
O(1)\n\neach dequeue takes O(1) if there is no stack transfer since pop is
O(1)\nHowever, in the event of a stack transfer, it can take O(n) in the wo
rst case.\nAs each element can only move to the dequeue stack once, if we a
dd one extra cyber-dollar for every\nenqueue, we will spend them exactly to
move all those elements to the dequeue stack, which means \nthat we've amor
tized is to O(c), or O(1)\n\nIn other words, O(n) dequeues will involve O(n
) pops and a total of O(n) pop, push operations to \nchange stacks. As a r
esult, each individual dequeue is amortized to O(1)\n\nA similar proof exis
ts for first()\n\n\n"
In [26]:
#--------------C6-26-----------------
"""
The solution to this is similar to that in C6-25; however, you may also pop
from the Enqueue stack and push onto the
dequeue stack. Otherwise, the same rules apply
Unfortunately, a pop or popleft now have running time O(n), since if you do
successive pop, popleft combos,
you will have to transfer the entire remaining stack. That would give n +
n-1 + n-2... + 2 + 1 operations to
pop the enture deque that way, which is equal to (n+1)(n) operations, or
O(n)
The reason that this is not amortized is the fact that we cannot guarantee
that each element will be transfered a fixed
number of steps
"""
class Empty(Exception):
pass
class DequeUsingStacks():
def __init__(self):
self._Dstack, self._nd = Stack(), 0 #Dequeuing Stack and
num_elements
self._Estack, self._ne = Stack(), 0 #Enqueuing Stack and
num_elements
def is_empty(self):
return (self._nd + self._ne) == 0
def popleft(self):
#If the dequeue stack is empty, pop all values over from the
enqueue stack
if self._nd == 0:
print('Performing a transfer')
if self._ne == 0: raise Empty('Your Queue is empty!')
self._stack_transfer()
#Once the Dequeue stack has been repopulated, pop the top value
ans = self._Dstack.pop()
self._nd -= 1
return ans
def pop(self):
#If the enqueue stack is empty, pop all values over from the
dequeue stack
if self._ne == 0:
if self._nd == 0: raise Empty('Your Queue is empty!')
self._stack_transfer(reverse = True)
#Once the Dequeue stack has been repopulated, pop the top value
ans = self._Estack.pop()
self._ne -= 1
return ans
def first(self):
if self._nd == 0:
if self._ne == 0: raise Empty('No first, Your Queue is empty!')
self._stack_transfer()
return self._Dstack.top()
Q= DequeUsingStacks()
for i in range(100):
Q.append(i)
"""
Q = Queue()
return found
S = Stack()
for i in range(10):
S.push(i)
class CappedArrayQueue():
DEFAULT_CAPACITY = 10
def __len__(self):
return self._size
def is_empty(self):
return self._size == 0
def first(self):
if self.is_empty(): raise Empty('The queue is empty')
return self._data[self._front]
def dequeue(self):
if self.is_empty(): raise Empty('The queue is empty')
answer = self._data[self._front]
self._data[self._front] = None #Help with GC
self._front = (self._front + 1)%len(self._data)
self._size -= 1
return answer
CAQ = CappedArrayQueue(50)
print('Enqueue order')
for i in range(60):
try:
CAQ.enqueue(i)
print('Dequeue order')
while not CAQ.is_empty():
print(CAQ.dequeue(), end = ', ')
Enqueue order
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,2
8,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,The Queue
has reached it's maximum length of 50
The Queue has reached it's maximum length of 50
The Queue has reached it's maximum length of 50
The Queue has reached it's maximum length of 50
The Queue has reached it's maximum length of 50
The Queue has reached it's maximum length of 50
The Queue has reached it's maximum length of 50
The Queue has reached it's maximum length of 50
The Queue has reached it's maximum length of 50
The Queue has reached it's maximum length of 50
Dequeue order
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 2
1, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
In [29]:
#--------------C6-29-------------------------
class ArrayQueueRotate():
DEFAULT_CAPACITY = 10
def __init__(self):
self._data = [None]*self.DEFAULT_CAPACITY
self._size = 0
self._front = 0
def __len__(self):
return self._size
def is_empty(self):
return self._size == 0
def first(self):
if self.is_empty(): raise Empty('The queue is empty')
return self._data[self._front]
def dequeue(self):
if self.is_empty(): raise Empty('The queue is empty')
answer = self._data[self._front]
self._data[self._front] = None #Help with GC
self._front = (self._front + 1)%len(self._data)
self._size -= 1
return answer
def rotate(self):
if self.is_empty(): raise Empty('The array is empty')
self._data[(self._front + self._size)%len(self._data)] =
self._data[self._front]
self._front = (self._front + 1)%len(self._data)
AQR = ArrayQueueRotate()
for i in range(100):
AQR.enqueue(i)
for i in range(300):
print (AQR.first(), end = ', ')
AQR.rotate()
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 2
1, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58,
59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96,
97, 98, 99, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 1
8, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74,
75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93,
94, 95, 96, 97, 98, 99, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 1
5, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
91, 92, 93, 94, 95, 96, 97, 98, 99,
In [30]:
#-----------C6-30-----------------------------
#Note, this solution relies on the ArrayQueueRotate class from question C6-
31
#Note, please lower num_games is this cell takes too long to run
"""
I'm not sure if this is the right answer, but if one of the queues only has
even numbers, if that queue was chosen
last, her chance of success would be 100%
"""
import random
return final_processed_value % 2 == 0
Q, R = ArrayQueueRotate(), ArrayQueueRotate()
Q.enqueue(0)
"""
Out[31]:
'\n1) Take Mazie and Daisy across (4 min) : total is 4\n2) Take M
azie back (2 min) : total is 6\n3) Take Lazy and Craz
y across (20 min) : total is 26\n4) Take Daisy back (4min)
: total is 30\n5) Take Mazie and Daisy across (4 min) : total is
34\n\n'
Projects
In [32]:
#--------------P6-32--------------------
"""
Note: we need to support the following functions:
add_first
add_last
delete_first
delete_last
first
last
is_empty()
len
"""
class ArrayDeque():
DEFAULT_CAPACITY = 10
def __init__(self):
self._data = [None]*self.DEFAULT_CAPACITY
self._front = 0
self._size = 0
def is_empty(self):
return self._size == 0
def __len__(self):
return self._size
def first(self):
if self.is_empty(): raise Empty('Deque is empty')
return self._data[self._front]
def last(self):
if self.is_empty(): raise Empty('Deque is empty')
return self._data[(self._front + self._size-1)%len(self._data)]
def remove_first(self):
if self.is_empty(): raise Empty('Deque is empty')
ans = self._data[self._front]
self._data[self._front] = None
self._front = (self._front+1)%len(self._data)
self._size -= 1
return ans
def remove_last(self):
if self.is_empty(): raise Empty('Deque is empty')
ans = self._data[(self._front+ self._size-1)%len(self._data)]
self._data[(self._front+ self._size)%len(self._data)] = None
self._size -= 1
return ans
DEQ = ArrayDeque()
print('Adding last')
for i in range(10):
DEQ.add_last(i)
print (i, DEQ._data)
len
appendleft
append
popleft
pop
D[0]
D[-1]
D[j]
D[j] = val
D.clear()
D.rotate(k)
D.remove(e)
D.count(e)
"""
class ArrayDequeMaxlen():
DEFAULT_CAPACITY = 10
def __init__(self, maxlen = None):
self._data = [None]*self.DEFAULT_CAPACITY
self._front = 0
self._size = 0
self._maxlen = maxlen
def is_empty(self):
return self._size == 0
def __len__(self):
return self._size
def popleft(self):
if self.is_empty(): raise Empty('Deque is empty')
ans = self._data[self._front]
self._data[self._front] = None
self._front = (self._front+1)%len(self._data)
self._size -= 1
return ans
def pop(self):
if self.is_empty(): raise Empty('Deque is empty')
ans = self._data[(self._front+ self._size-1)%len(self._data)]
self._data[(self._front+ self._size-1)%len(self._data)] = None
self._size -= 1
return ans
def clear(self):
self._data = [None]*len(self._data)
self._size = 0
self._front = 0
return total_count
AQM = ArrayDequeMaxlen(20)
print('Adding last')
for i in range(100):
AQM.append(i)
print (i, AQM._data)
AQM.clear()
print('\nCleared Data:', AQM._data)
for i in range(100):
AQM.append(i%3)
print(AQM._front)
print ('\nRotating')
for i in range(20):
AQM.rotate(1)
print('Front is:', AQM[0])
Delete 80 None [99, None, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 9
3, 94, 95, 96, 97, 98] 2
Cleared Data: [None, None, None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None, None, None]
Found 7 2s in [0, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2]
Adding first
20 [2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 20]
19 [20, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 19]
18 [19, 20, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 18]
17 [18, 19, 20, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 17]
16 [17, 18, 19, 20, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 16]
15 [16, 17, 18, 19, 20, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 15]
14 [15, 16, 17, 18, 19, 20, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 14]
13 [14, 15, 16, 17, 18, 19, 20, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 13]
12 [13, 14, 15, 16, 17, 18, 19, 20, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 12]
11 [12, 13, 14, 15, 16, 17, 18, 19, 20, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 11]
19
Rotating
Front is: 2
Front is: 1
Front is: 0
Front is: 2
Front is: 1
Front is: 0
Front is: 2
Front is: 1
Front is: 0
Front is: 2
Front is: 20
Front is: 19
Front is: 18
Front is: 17
Front is: 16
Front is: 15
Front is: 14
Front is: 13
Front is: 12
Front is: 11
"""
Out[34]:
'\n\nNote, this was already implemented in the answer for C6-22\n\n\n'
In [35]:
#----------P6-35---------------------
class Empty(Exception): pass
class LeakyStack():
def __init__(self, capacity = 20):
self._data = [None]*capacity
self._capacity = capacity
self._front = 0
self._size = 0
def __len__(self):
return self._size
def is_empty(self):
return self._size == 0
def pop(self):
if self.is_empty(): raise Empty('Stack is empty')
ans = self._data[(self._front + self._size -1)%len(self._data)]
self._data[(self._front + self._size -1)%len(self._data)] = None
self._size -= 1
return ans
undo = LeakyStack(30)
for i in range(100):
undo.push(i)
Here we have decided to modify the front value of the queue if there is a
partial sale.
Otherwise, we would just dequeue, modify the value and then rotate it bacak
to the front (which would take
much longer, but is more in line with that a Queue should be able to do)
We could also have just used a double-ended queue and used popleft,
appendleft to bring out and put back the
modified value; however, the questions recommended we use the FIFO protocol
"""
class CapitalGainCalculator():
def __init__(self):
self.Q = ArrayQueueRotate() #From exercise C6-29
self._shares = 0
def __call__(self, message):
"""
Note, if we split by ' ',
we can assume that the message is always in the following order:
message[0] = Buy or Sell
message[1] = Number
message[4] = Price
"""
try:
preprice = message[4]
if preprice.startswith('$'): preprice = preprice[1:]
price = float(preprice)
except:
print(f'Fourth command should be a price. Recieved:
{message[4]}')
return False
total_capital_gain = 0
number = sell_num
while number>0:
buy_num, buy_price = self.Q.first()
if buy_num < number: #You can sell the entire block
self.Q.dequeue()
num_sold = buy_num
else:
num_sold = number
self._partial_sale((buy_num-number, buy_price))
self._shares -= sell_num
return total_capital_gain
Challenges:
"""
class ArrayDoubleStack():
DEFAULT_CAPACITY = 20
GROW_RATE = 2
def __init__(self):
self._sizer = 0
self._sizeb = 0
self._data = [None]*self.DEFAULT_CAPACITY
self._rfront = 0
self._bfront = self.DEFAULT_CAPACITY //2
def is_empty_blue(self):
return self._sizeb == 0
def is_empty_red(self):
return self._sizer == 0
def is_full(self):
return (self._sizer + self._sizeb) == len(self._data)
def pop_red(self):
if self.is_empty_red(): raise Empty('Red is empty')
ans = self._pop(self._rfront, self._sizer)
self._sizer -= 1
return ans
def pop_blue(self):
if self.is_empty_bue(): raise Empty('Blue is empty')
ans = self._pop(value, self._bfront, self._sizeb)
self._sizeb -= 1
return ans
"""
new_array = [None]*capacity
for i in range(self._sizer):
new_array[i] = self._data[(self._rfront + i)%len(self._data)]
for i in range(self._sizeb):
new_array[i + new_frontb] =
self._data[(self._bfront+i)%len(self._data)]
self._rfront = 0
self._bfront = new_frontb
self._data = new_array
dA = ArrayDoubleStack()
for i in range(11):
dA.push_blue(i)
dA.push_red(100+i)
print(dA._data, dA._sizer, dA._sizeb, '\n')
[100, 101, None, None, None, None, None, None, None, None, 0, 1, None, None
, None, None, None, None, None, None] 2 2
[100, 101, 102, None, None, None, None, None, None, None, 0, 1, 2, None, No
ne, None, None, None, None, None] 3 3
[100, 101, 102, 103, None, None, None, None, None, None, 0, 1, 2, 3, None,
None, None, None, None, None] 4 4
[100, 101, 102, 103, 104, None, None, None, None, None, 0, 1, 2, 3, 4, None
, None, None, None, None] 5 5
[100, 101, 102, 103, 104, 105, None, None, None, None, 0, 1, 2, 3, 4, 5, No
ne, None, None, None] 6 6
[100, 101, 102, 103, 104, 105, 106, None, None, None, 0, 1, 2, 3, 4, 5, 6,
None, None, None] 7 7
[100, 101, 102, 103, 104, 105, 106, 107, None, None, 0, 1, 2, 3, 4, 5, 6, 7
, None, None] 8 8
[100, 101, 102, 103, 104, 105, 106, 107, 108, None, 0, 1, 2, 3, 4, 5, 6, 7,
8, None] 9 9
[100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 0, 1, 2, 3, 4, 5, 6, 7,
8, 9] 10 10
[100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, None, None, None, N
one, None, None, None, None, None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, None,
None, None, None, None, None, None, None, None] 11 11
110
109
108
107
106
105
104
103
102
101
100
End of chapter 6!