0% found this document useful (0 votes)
28 views54 pages

Chapter 6 Exercises

Chapter 6 Exercises cover various implementations and exercises related to Stack and Queue data structures. It includes examples of stack operations, recursive methods for popping elements, and checks for valid arithmetic expressions using stacks. The exercises also highlight the importance of exception handling and performance considerations in data structure operations.

Uploaded by

Pinaki
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
28 views54 pages

Chapter 6 Exercises

Chapter 6 Exercises cover various implementations and exercises related to Stack and Queue data structures. It includes examples of stack operations, recursive methods for popping elements, and checks for valid arithmetic expressions using stacks. The exercises also highlight the importance of exception handling and performance considerations in data structure operations.

Uploaded by

Pinaki
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 54

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

If 3 pop operations failed, we only effectively had 7 complete pops


Therefore, we should have 25-7 = 18 elements in the stack

"""
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 push(self, value):


self._data.append(value)

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

def transfer(S, T):


while not S.is_empty():
T.push(S.pop())

S,T = Stack(), Stack()

try: S.pop()
except Exception as e: print (e)

for i in range(20):
S.push(i)

print('Top of S is: ', S.top())


transfer(S, T)
print('Top of T is: ', T.top())
S.full_pop(), T.full_pop()
List is empty
Top of S is: 19
Top of T is: 0
Out[3]:
([], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
)
In [4]:
#---------R6-4-----------------------
"""
Note: I have shown how to remove all elements from the stack using a loop,
but
this exercise asks to do it recursively

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)

"""

#Option 1, simple O(n) for normal or O(n^2) for in_order=True


class RStack(Stack):
def full_pop(self, in_order = False): #override the Stack.full_pop
method
if self.is_empty():
return []
else:
ans = [self.pop()]
data = self.full_pop(in_order)
if in_order: ans, data = data, ans
data.extend(ans)
return data

#Option 2, O(n) for outputs in order


class R2Stack(Stack):
def _full_pop_r(self, results, counter):
if self.is_empty():
return None
else:
results[counter] = self.pop()
counter += 1
self._full_pop_r(results, counter)

def full_pop(self): #override the Stack.full_pop method


results = [None]*len(self)
counter = 0
self._full_pop_r(results, counter)
return results

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)

ex.. (5+4)*(3/(2+3)) is correct

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

An alternative approach using recursion is to focus more on the (sequence


that produces a num) operator (sequence that produces a num)

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)

OPEN, CLOSE, OPERATORS = '(', ')', '+-/*^'


NUMBERS = '1234567890.'
def pop_until_open(self):
operator = True
while not self.is_empty():
ans = self.pop()
if ans in self.OPERATORS:
if operator: return False
else: operator = True

elif ans in self.NUMBERS:


operator = False
elif ans in self.CLOSE: return False
elif ans in self.OPEN:
self.push('3')
return True

return False #if you don't find an open bracket, there is a


mismatch

def check_expression(self, expression):


for char in expression:

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

First operations don't change the queue at all

Therefore we have 32-10 = 22 (the size of the queue is 22 at that point)

"""
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])

ex. a queue with 1,2,3,4,5,6,7 with front = 3:


F
5,6,7,1,2,3,4

would become:
F
5,6,7,1,2,3,4,N,N,N,N,N,N,N

the next insertion would be at front + self._size, which would be:


F
5,6,7,1,2,3,4,N,N,N,8,N,N,N and so on, which results in the fragmentation
of the data. Successive dequeues would
result in 1,2,3,4,N,N,N,....., N,N instead of the desired 1,2,3,4,5,6,7,
is_empty==True

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 enqueue(self, value):


self._size += 1
self._data.append(value) #Note we append here to add an element to
the end of the queue

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)

print('First', dq.first(), 'Length', len(dq))


while not dq.is_empty():
print( dq.dequeue(), end = ', ')
First 0 Length 10
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
In [12]:
#---------------R6-12----------------
"""

Return Values in the Stack


- [4]
- [4, 8]
- [4, 8, 9]
- [5, 4, 8, 9]
9 [5, 4, 8, 9] (the back operation - I'm assuming they meant last()
based on their implementation)
5 [4, 8, 9]
9 [4,8]
- [4,8,7]
4 [4,8,7]
7 [4,8,7]
- [4,8,7,6]
4 [8,7,6]
8 [7,6]

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

def rearrange_using_queue(D, Q):


for i in range(5): # D Q
Q.enqueue(D.popleft()) #[6,7,8], [1,2,3,4,5]

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)

def rearrange_using_stack(D, S):


for _ in range(4):
S.push(D.popleft()) # [5,6,7,8], [1,2,3,4]

D.append(S.pop()) # [5,6,7,8,4], [1,2,3]

S.push(D.popleft()) # [6,7,8,4], [1,2,3,5]

S.push(D.pop()) # [6,7,8], [1,2,3,5,4]

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

As a result, the overall probability of success is (2/3)*1 + (1/3)*0 = 2/3


as expected

"""

import random

def max_picker(S, numbers):


#randomize
random.shuffle(numbers)
true_max = max(numbers)

#set up the stack


for num in numbers:
S.push(num)

#Perform the test:


x = S.pop()
new = S.pop()
return new if new>x else x

#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

print (f'The probability of you picking the correct number is:


{total/num_trials*100}%')
The probability of you picking the correct number is: 65.3%
In [16]:
#-----------------C6-16--------------------
class Empty(Exception):
pass

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 push(self, e):


if self._maxlen is not None and len(self) == self._maxlen:
raise Full('The array is full')
self._data.append(e)

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 push(self, value):


if self._n == self._capacity:
raise Full('The stack is full')
self._data[self._n] = value
self._n += 1

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

Original -> S1 = Reversed Order


S1 -> S2 = Original Order
S2 -> Original = Reversed Order

"""

s, s_orig = Stack(), Stack() #Need Stack from R6-3

for i in range(10):
s.push(i)
s_orig.push(i)

def reverse_stack(S):
s1 = Stack()
s2 = Stack()

while not S.is_empty(): s1.push(S.pop())


while not s1.is_empty(): s2.push(s1.pop())
while not s2.is_empty(): S.push(s2.pop())

reverse_stack(s)

print('Reversed stack vs. original')


while not s.is_empty():
print ('N:', s.pop(), '\tO:', s_orig.pop())

Reversed stack vs. original


N: 0 O: 9
N: 1 O: 8
N: 2 O: 7
N: 3 O: 6
N: 4 O: 5
N: 5 O: 4
N: 6 O: 3
N: 7 O: 2
N: 8 O: 1
N: 9 O: 0
In [19]:
#---------C6-19------------------
"""
Note, based on the examples, we will assume that the tag is the first word
and always terminates with a space

"""

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

s = raw.find(' ', j+1)


tag = raw[j+1: s if 0<=s<k else k]
#print (tag)
if not tag.startswith('/'): S.push(tag)
else:
if S.is_empty():
return False #You closed a tag that has no opening
if tag[1:] != S.pop(): #You need to remove the forwardslash
from the tag
return False
j = raw.find('<', k+1)

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

An explicit stack means that we can use push and pop

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.

The 'Base Case' is when the list of numbers leftover is 0

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

for num in nums:


S.push(([num], nums-set([num])))

while not S.is_empty():


l, remaining = S.pop()
if len(remaining) == 0:
print (l)
else:
for n in remaining:
l2 = l.copy()
l2.append(n)
S.push((l2, nums-set(l2)))

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)

#Repopulate the Queue for the next element


while not S.is_empty():
Q.enqueue(S.pop())

#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()

def _push(self, value):


self._S.push(value)

def _evaluate(self, operator):


pexp2 = self._pop()
pexp1 = self._pop()

self._push(self.OPERATORS[operator](pexp1, pexp2)) #Add the result


back to the stack

def __call__(self, operation):


self._S = Stack() #Need to reset the stack for the new operator!
for item in operation:
if item in self.OPERATORS:
self._evaluate(item)
elif isinstance(item, int) or isinstance(item, float):
self._push(item)

#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 exp in exps:


try:
print(exp, '=', pf_assess(exp))
except Exception as e:
print (exp, 'failed with the following exception:', e)
[5, 2, '+', 8, 3, '-', '*', 4, '/'] = 8.75
[5, 2, 3, '+', 8, 3, '-', '*', 4, '/', '**'] = 23364.82470658157
[5, 2, 3, '+', 8, 3, '-', '*', 4, '/'] failed with the following exception:
Invalid Expression: you have entered more values than the operator processe
d
[5, 2, '+', 8, 3, '-', '*', 4, '/', '*'] failed with the following exceptio
n: Operator without value
In [23]:
#-------------C6-23--------------------
"""
Note, we have to accomplish this using stacks

Remember that for stacks, the left-most number is at the top

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

"""

def transfer_below(R, S, T):


"""
We can treat the final stack as the target and the new_elements as an
array

"""

#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

#Step 2: Transfer T to R. If we transfer directly to S, it will be


reversed
len_T = 0
while not T.is_empty():
R.push(T.pop())
len_T += 1
#Step 3: Transfer the values from each stack back to S
for _ in range(len_T + len_S):
S.push(R.pop())

def initialize_stacks(R, S, T, r, s, t):


for i in r:
R.push(i)
for i in s:
S.push(i)
for i in t:
T.push(i)

R, S, T = Stack(), Stack(), Stack()


initialize_stacks(R, S, T, [1,2,3], [4,5], [6,7,8,9])

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

To pop, you just dequeue as normal


"""
class Empty(Exception):
pass

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 push(self, value):


self._data.enqueue(value)
self._n += 1
for _ in range(self._n - 1): #note that if n == 1, this does not
happen
self._data.enqueue(self._data.dequeue())

def top(self):
return self._data.first()

def __len__(self):
return self._n

s = StackUsingQueue()

for i in range(10):
s.push(i)

while not s.is_empty():


print(s.top(), s.pop())

"""
With this implementation:

top() is O(1) amortized


pop() is O(1) amortized
push() is O(n)

So actually, it's really only the pushes that truly suffer

"""

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

Therefore, to implement this, we have a Enqueue-ing stack and a Dequeu-ing


stack. When a pop is called, we
either pop from the Dequeue stack, or if there are no elements, transfer
all of the Enqueue stack items to the
dequeue stack. The secret is to only make the transfer when the Dequeue
stack is empty, since it would mess up
the order otherwise (ex. if D_Stack has [3,2,1] and you load [4, 5, 6], it
will become [3,2,1,6,5,4], which will
obviously not pop the 1 that you want next)

"""

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 enqueue(self, value):


self._Estack.push(value)
self._ne += 1

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)

while not Q.is_empty():


print(Q.first(), Q.dequeue())

"""
With this implementation:

each enqueue takes O(1) since a push takes O(1)

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 number of transfers required can be seen in the testcase below

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 append(self, value):


self._Estack.push(value)
self._ne += 1

def appendleft(self, value):


self._Dstack.push(value)
self._nd += 1

def _stack_transfer(self, reverse = False):


if reverse == False:
while self._ne >0:
self._Dstack.push(self._Estack.pop())
self._ne -= 1
self._nd += 1
else:
while self._nd >0:
self._Estack.push(self._Dstack.pop())
self._nd -= 1
self._ne += 1

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)

print(Q._Estack._data, Q._ne, Q._nd)

while not Q.is_empty():


try:
print('Popleft:', Q.popleft())
print('Pop: ', Q.pop())
except Empty as e:
print(e)
[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, 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] 100 0
Performing a transfer
Popleft: 0
Pop: 99
Performing a transfer
Popleft: 1
Pop: 98
Performing a transfer
Popleft: 2
Pop: 97
Performing a transfer
Popleft: 3
Pop: 96
Performing a transfer
Popleft: 4
Pop: 95
Performing a transfer
Popleft: 5
Pop: 94
Performing a transfer
Popleft: 6
Pop: 93
Performing a transfer
Popleft: 7
Pop: 92
Performing a transfer
Popleft: 8
Pop: 91
Performing a transfer
Popleft: 9
Pop: 90
Performing a transfer
Popleft: 10
Pop: 89
Performing a transfer
Popleft: 11
Pop: 88
Performing a transfer
Popleft: 12
Pop: 87
Performing a transfer
Popleft: 13
Pop: 86
Performing a transfer
Popleft: 14
Pop: 85
Performing a transfer
Popleft: 15
Pop: 84
Performing a transfer
Popleft: 16
Pop: 83
Performing a transfer
Popleft: 17
Pop: 82
Performing a transfer
Popleft: 18
Pop: 81
Performing a transfer
Popleft: 19
Pop: 80
Performing a transfer
Popleft: 20
Pop: 79
Performing a transfer
Popleft: 21
Pop: 78
Performing a transfer
Popleft: 22
Pop: 77
Performing a transfer
Popleft: 23
Pop: 76
Performing a transfer
Popleft: 24
Pop: 75
Performing a transfer
Popleft: 25
Pop: 74
Performing a transfer
Popleft: 26
Pop: 73
Performing a transfer
Popleft: 27
Pop: 72
Performing a transfer
Popleft: 28
Pop: 71
Performing a transfer
Popleft: 29
Pop: 70
Performing a transfer
Popleft: 30
Pop: 69
Performing a transfer
Popleft: 31
Pop: 68
Performing a transfer
Popleft: 32
Pop: 67
Performing a transfer
Popleft: 33
Pop: 66
Performing a transfer
Popleft: 34
Pop: 65
Performing a transfer
Popleft: 35
Pop: 64
Performing a transfer
Popleft: 36
Pop: 63
Performing a transfer
Popleft: 37
Pop: 62
Performing a transfer
Popleft: 38
Pop: 61
Performing a transfer
Popleft: 39
Pop: 60
Performing a transfer
Popleft: 40
Pop: 59
Performing a transfer
Popleft: 41
Pop: 58
Performing a transfer
Popleft: 42
Pop: 57
Performing a transfer
Popleft: 43
Pop: 56
Performing a transfer
Popleft: 44
Pop: 55
Performing a transfer
Popleft: 45
Pop: 54
Performing a transfer
Popleft: 46
Pop: 53
Performing a transfer
Popleft: 47
Pop: 52
Performing a transfer
Popleft: 48
Pop: 51
Performing a transfer
Popleft: 49
Pop: 50
In [27]:
#--------------C6-27--------------------
"""
The trick here is that if you move elements from a Stack to a Queue and
then back to a Stack, the order will
be reversed. As a result, if we want to reorder the original stack, we
have to do that process twice!

Our additional variables will be:


whether it's found

"""

def find_element(S, element):


found = False

Q = Queue()

#Note, we have to do this twice. We don't really need to scan for


value == element the second time, but it
#makes the code more concise
for _ in range(2):
while not S.is_empty():
value = S.pop()
if value == element: found = True #Note, we cannot return here,
we have to keep going to reorder the stack!
Q.enqueue(value)

while not Q.is_empty():


S.push(Q.dequeue())

return found

S = Stack()
for i in range(10):
S.push(i)

for val in [2,3,5,6,10, 14, 55]:


print (f'Val {val} in the stack: {find_element(S, val)}')

print('\nTo test that the stack is still in order:')


while not S.is_empty():
print (S.pop())
Val 2 in the stack: True
Val 3 in the stack: True
Val 5 in the stack: True
Val 6 in the stack: True
Val 10 in the stack: False
Val 14 in the stack: False
Val 55 in the stack: False

To test that the stack is still in order:


9
8
7
6
5
4
3
2
1
0
In [28]:
#--------------C6-28------------------------------
class Empty(Exception): pass
class Full(Exception): pass

class CappedArrayQueue():
DEFAULT_CAPACITY = 10

def __init__(self, maxlen = 100):


self._data = [None]*self.DEFAULT_CAPACITY
self._size = 0
self._front = 0
self._maxlen = maxlen

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 enqueue(self, value):


if self._size == self._maxlen: raise Full(f"The Queue has reached
it's maximum length of {self._maxlen}")
if self._size == len(self._data): self._resize(min(self._size * 2,
self._maxlen)) #don't make it bigger than the maxlen
self._data[(self._front + self._size)%len(self._data)] = value
self._size += 1

def _resize(self, capacity):


new_array = [None]*capacity
for i in range(self._size):
new_array[i] = self._data[(self._front + i)%len(self._data)]
self._data = new_array
self._front = 0

CAQ = CappedArrayQueue(50)
print('Enqueue order')
for i in range(60):
try:
CAQ.enqueue(i)

print(i, end = ',')


except Full as e:
print(e)

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 Empty(Exception): pass


class Full(Exception): pass

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 enqueue(self, value):


if self._size == len(self._data): self._resize(self._size * 2)
self._data[(self._front + self._size)%len(self._data)] = value
self._size += 1

def _resize(self, capacity):


new_array = [None]*capacity
for i in range(self._size):
new_array[i] = self._data[(self._front + i)%len(self._data)]
self._data = new_array
self._front = 0

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%

In a mixed situation, the chance of success is even/(even + odd).

There is a 50% chance that each queue will be chosen last

So to maximize the probability of winning, we should populate one queue


with 1 even integer and the
other queue with 49 evens and 50 odds

That way, her probability of winning is

(0.5)*1 + (0.5)*(49/99) = 0.7475 -> She will win approximately 74.75% of


the time

"""

import random

def play_queue_game(Q, R, num_turns = 100, max_rotations = 20):


for _ in range(num_turns):
current_array = Q if random.random()>0.5 else R
for _ in range(random.randint(0, max_rotations)):
final_processed_value = current_array.first()
current_array.rotate()

return final_processed_value % 2 == 0

Q, R = ArrayQueueRotate(), ArrayQueueRotate()

Q.enqueue(0)

for i in range(1, 100):


R.enqueue(i)
total_wins = 0
num_games = 10000
for game in range(num_games):
if play_queue_game(Q, R): total_wins+=1

print(f'Alice won {total_wins/num_games*100}% of her games')

Alice won 74.98% of her games


In [31]:
#---------------C6-31--------------------
"""
1) Take Mazie and Daisy across (4 min) : total is 4
2) Take Mazie back (2 min) : total is 6
3) Take Lazy and Crazy across (20 min) : total is 26
4) Take Daisy back (4min) : total is 30
5) Take Mazie and Daisy across (4 min) : total is 34

"""
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 Empty(Exception): pass

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 add_first(self, value):


if self._size == len(self._data): self._resize(self._size *2)
self._data[(self._front-1)%len(self._data)] = value
self._front = (self._front - 1)%len(self._data)
self._size += 1

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 add_last(self, value):


if self._size == len(self._data): self._resize(self._size*2)
self._data[(self._front+self._size)%len(self._data)] = value
self._size += 1

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

def _resize(self, capacity):


old = self._data
self._data = [None]*capacity
for i in range(len(old)):
self._data[i] = old[(self._front+i)%len(old)]
self._front = 0

DEQ = ArrayDeque()
print('Adding last')
for i in range(10):
DEQ.add_last(i)
print (i, DEQ._data)

print ('Adding first')


for i in range(20, 10, -1):
DEQ.add_first(i)
print (i, DEQ._data)

print('Performing the removals')


while not DEQ.is_empty():
print ('Remove first', DEQ.first(), DEQ.remove_first(), 'Remove last',
DEQ.last(), DEQ.remove_last())
Adding last
0 [0, None, None, None, None, None, None, None, None, None]
1 [0, 1, None, None, None, None, None, None, None, None]
2 [0, 1, 2, None, None, None, None, None, None, None]
3 [0, 1, 2, 3, None, None, None, None, None, None]
4 [0, 1, 2, 3, 4, None, None, None, None, None]
5 [0, 1, 2, 3, 4, 5, None, None, None, None]
6 [0, 1, 2, 3, 4, 5, 6, None, None, None]
7 [0, 1, 2, 3, 4, 5, 6, 7, None, None]
8 [0, 1, 2, 3, 4, 5, 6, 7, 8, None]
9 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Adding first
20 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, None, None, None, None, None, None, None,
None, None, 20]
19 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, None, None, None, None, None, None, None,
None, 19, 20]
18 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, None, None, None, None, None, None, None,
18, 19, 20]
17 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, None, None, None, None, None, None, 17, 1
8, 19, 20]
16 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, None, None, None, None, None, 16, 17, 18,
19, 20]
15 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, None, None, None, None, 15, 16, 17, 18, 1
9, 20]
14 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, None, None, None, 14, 15, 16, 17, 18, 19,
20]
13 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, None, None, 13, 14, 15, 16, 17, 18, 19, 2
0]
12 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, None, 12, 13, 14, 15, 16, 17, 18, 19, 20]
11 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
Performing the removals
Remove first 11 11 Remove last 9 9
Remove first 12 12 Remove last 8 8
Remove first 13 13 Remove last 7 7
Remove first 14 14 Remove last 6 6
Remove first 15 15 Remove last 5 5
Remove first 16 16 Remove last 4 4
Remove first 17 17 Remove last 3 3
Remove first 18 18 Remove last 2 2
Remove first 19 19 Remove last 1 1
Remove first 20 20 Remove last 0 0
In [33]:
#-------------P6-33-------------------
"""
We have to support the following methods:

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 Empty(Exception): pass

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 __getitem__(self, index):


if index <0: index = self._size + index #Negative indices
if not 0<=index<self._size: raise IndexError('Invalid index')
return (self._data[(self._front + index)%len(self._data)])

def __setitem__(self, index, value):


if index <0: index = self._size + index #Negative indices
if not 0<=index<self._size: raise IndexError('Invalid index')
self._data[(self._front + index)%len(self._data)] = value

def appendleft(self, value):


if self._size == len(self._data): self._resize(self._size *2)
self._data[(self._front-1)%len(self._data)] = value
self._front = (self._front - 1)%len(self._data)
self._size = self._size + 1 if self._maxlen is None else
min(self._size+1, self._maxlen)

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 rotate(self, k):


for _ in range(k):
ans = self.pop()
self.appendleft(ans)

def append(self, value):


if self._size == len(self._data): self._resize(self._size*2)
self._data[(self._front+self._size)%len(self._data)] = value

if self._maxlen is not None and self._size == self._maxlen:


self._front += 1 #if you were at maxlen, you overwrote the previous first
else: self._size = self._size + 1

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 _resize(self, capacity):


if self._maxlen is not None: capacity = min (capacity,
self._maxlen)
old = self._data
self._data = [None]*capacity
for i in range(len(old)):
self._data[i] = old[(self._front+i)%len(old)]
self._front = 0

def clear(self):
self._data = [None]*len(self._data)
self._size = 0
self._front = 0

def remove(self, value):


if self.is_empty(): raise Empty('Deque is empty')
found = False
for i in range(self._size):
ans = self.pop()
if ans == value and not found:
found = True #Do not remove subsequent finds
else: self.appendleft(ans)

def count(self, value):


if self.is_empty(): raise Empty('Deque is empty')
total_count = 0
for i in range(self._size):
ans = self.pop()
if ans == value: total_count+= 1
self.appendleft(ans)

return total_count

AQM = ArrayDequeMaxlen(20)

print('Adding last')
for i in range(100):
AQM.append(i)
print (i, AQM._data)

print('\nDelete 80', AQM.remove(80), AQM._data, AQM._front)

AQM.clear()
print('\nCleared Data:', AQM._data)

for i in range(100):
AQM.append(i%3)

print('\nFound', AQM.count(2), '2s in ', AQM._data)

print ('\nAdding first')


for i in range(20, 10, -1):
AQM.appendleft(i)
print (i, AQM._data)

print(AQM._front)

print ('\nRotating')
for i in range(20):
AQM.rotate(1)
print('Front is:', AQM[0])

print('\nPerforming the removals')


while not AQM.is_empty():
print ('Remove first', AQM[0], AQM.popleft(), 'Remove last', AQM[-1],
AQM.pop())
Adding last
0 [0, None, None, None, None, None, None, None, None, None]
1 [0, 1, None, None, None, None, None, None, None, None]
2 [0, 1, 2, None, None, None, None, None, None, None]
3 [0, 1, 2, 3, None, None, None, None, None, None]
4 [0, 1, 2, 3, 4, None, None, None, None, None]
5 [0, 1, 2, 3, 4, 5, None, None, None, None]
6 [0, 1, 2, 3, 4, 5, 6, None, None, None]
7 [0, 1, 2, 3, 4, 5, 6, 7, None, None]
8 [0, 1, 2, 3, 4, 5, 6, 7, 8, None]
9 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
10 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, None, None, None, None, None, None, N
one, None, None]
11 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, None, None, None, None, None, Non
e, None, None]
12 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, None, None, None, None, None,
None, None]
13 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, None, None, None, None, N
one, None]
14 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, None, None, None, Non
e, None]
15 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, None, None, None,
None]
16 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, None, None, N
one]
17 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, None, Non
e]
18 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, None]
19 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
20 [20, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
21 [21, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
22 [22, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21
]
23 [23, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 2
2]
24 [24, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23]
25 [25, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24]
26 [26, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24
, 25]
27 [27, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 2
5, 26]
28 [28, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27]
29 [29, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28]
30 [30, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
28, 29]
31 [31, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
29, 30]
32 [32, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31]
33 [33, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32]
34 [34, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33]
35 [35, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
33, 34]
36 [36, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
34, 35]
37 [37, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36]
38 [38, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37]
39 [39, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
37, 38]
40 [40, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
38, 39]
41 [41, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40]
42 [42, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
40, 41]
43 [43, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42]
44 [44, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
42, 43]
45 [45, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44]
46 [46, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
44, 45]
47 [47, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
45, 46]
48 [48, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
46, 47]
49 [49, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
47, 48]
50 [50, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49]
51 [51, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
49, 50]
52 [52, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
50, 51]
53 [53, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52]
54 [54, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
52, 53]
55 [55, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
53, 54]
56 [56, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
54, 55]
57 [57, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
55, 56]
58 [58, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
56, 57]
59 [59, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
57, 58]
60 [60, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
58, 59]
61 [61, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58,
59, 60]
62 [62, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61]
63 [63, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
61, 62]
64 [64, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
62, 63]
65 [65, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
63, 64]
66 [66, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
64, 65]
67 [67, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64,
65, 66]
68 [68, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
66, 67]
69 [69, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
67, 68]
70 [70, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67,
68, 69]
71 [71, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68,
69, 70]
72 [72, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
70, 71]
73 [73, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
71, 72]
74 [74, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
72, 73]
75 [75, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 74]
76 [76, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73,
74, 75]
77 [77, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74,
75, 76]
78 [78, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75,
76, 77]
79 [79, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76,
77, 78]
80 [80, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
78, 79]
81 [81, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78,
79, 80]
82 [82, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
80, 81]
83 [83, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
81, 82]
84 [84, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81,
82, 83]
85 [85, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82,
83, 84]
86 [86, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
84, 85]
87 [87, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,
85, 86]
88 [88, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85,
86, 87]
89 [89, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86,
87, 88]
90 [90, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87,
88, 89]
91 [91, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
89, 90]
92 [92, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
90, 91]
93 [93, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
91, 92]
94 [94, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91,
92, 93]
95 [95, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92,
93, 94]
96 [96, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93,
94, 95]
97 [97, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94,
95, 96]
98 [98, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
96, 97]
99 [99, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96,
97, 98]

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

Performing the removals


Remove first 11 11 Remove last 2 2
Remove first 12 12 Remove last 1 1
Remove first 13 13 Remove last 0 0
Remove first 14 14 Remove last 2 2
Remove first 15 15 Remove last 1 1
Remove first 16 16 Remove last 0 0
Remove first 17 17 Remove last 2 2
Remove first 18 18 Remove last 1 1
Remove first 19 19 Remove last 0 0
Remove first 20 20 Remove last 2 2
In [34]:
#------------P6-34------------------
"""

Note, this was already implemented in the answer for C6-22

"""
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 push(self, value):


self._data[(self._front + self._size)%len(self._data)] = value
if self._size == self._capacity: self._front += 1
else: self._size += 1

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)

print ('Leakiness check')


while not undo.is_empty():
print(undo.pop())
Leakiness check
99
98
97
96
95
94
93
92
91
90
89
88
87
86
85
84
83
82
81
80
79
78
77
76
75
74
73
72
71
70
In [36]:
#------------------P6-36-------------------
"""
FIFO means we use a queue. We have already defined all the funtions for a
queue before, so we
will just rely on those

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

"""

message = message.split(' ')


if message[0].lower() == 'buy': f = self.buy_shares
elif message[0].lower() == 'sell': f = self.sell_shares
else:
print(f'First command was not buy/sell. Received:
{message[0]}')
return False

try: number = int(message[1]) #shares can only be ints for this


problem
except:
print(f'Second command should be an integer. Recieved:
{message[1]}')
return False

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

return f(number, price)

def _partial_sale(self, block):


#Note, we assume that you've checked whether this is a valid
operation
self.Q._data[self.Q._front] = block

def sell_shares(self, sell_num, sell_price):


print(f'Attempting to sell {sell_num} shares at ${sell_price}')

total_capital_gain = 0
number = sell_num

if number > self._shares:


print(f'You only have {self._shares} to sell')
return 0 #Note: should you do a partial sale????

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

total_capital_gain += (sell_price - buy_price) * num_sold


number -= num_sold

print('\t', f'Your capital gains on that transaction were


${total_capital_gain}')

self._shares -= sell_num
return total_capital_gain

def buy_shares(self, buy_num, buy_price):


print(f"Attempting to buy {buy_num} shares at ${buy_price}")
self.Q.enqueue((buy_num, buy_price))
self._shares += buy_num
return True

SB = CapitalGainCalculator() #SB is for sharebot

seq = [('buy 200 shares at 20 each'),


('buy 200 shares at $15 each'),
('sell 100 shares at 100 each'),
('sell 200 shares at 10 each'),
('sell 100 shares at 10 each'),
('smell 100 shares at 10 each'),
('sell 100.5 shares at 10 each'),
('sell 100 shares at $10$ each'),
]

for transaction in seq:


SB(transaction)

Attempting to buy 200 shares at $20.0


Attempting to buy 200 shares at $15.0
Attempting to sell 100 shares at $100.0
Your capital gains on that transaction were $8000.0
Attempting to sell 200 shares at $10.0
Your capital gains on that transaction were $-1500.0
Attempting to sell 100 shares at $10.0
Your capital gains on that transaction were $-500.0
First command was not buy/sell. Received: smell
Second command should be an integer. Recieved: 100.5
Fourth command should be a price. Recieved: $10$
In [37]:
#-------------P6-37-------------------
"""
This question asks us to manage two stacks in the same array. The
operations we need are pop

Challenges:

collisions between red and blue -> automatic resize?


how to resize effectively to minimize future collisions (spaced out
evenly??)

"""

class Empty(Exception): pass

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 push_red(self, value):


idx = (self._rfront + self._sizer)%len(self._data)
if self.is_full() or idx == self._bfront :
self._resize(len(self._data)*self.GROW_RATE)

new_idx = (self._rfront + self._sizer)%len(self._data)


self._data[new_idx] = value
self._sizer += 1

def push_blue(self, value):


idx = (self._bfront + self._sizeb)%len(self._data)
if self.is_full() or idx == self._rfront :
self._resize(len(self._data)*self.GROW_RATE)

new_idx = (self._bfront + self._sizeb)%len(self._data)


self._data[new_idx] = value
self._sizeb += 1

def _pop(self, front, size):


ans = self._data[(front + size-1)%len(self._data)]
self._data[(front + size-1)%len(self._data)] = None
return ans

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

def _resize (self, capacity):


"""
Note, red walks, then blue takes half of the remaining space

"""

new_array = [None]*capacity

for i in range(self._sizer):
new_array[i] = self._data[(self._rfront + i)%len(self._data)]

new_frontb = self._sizer + (capacity-self._sizer - self._sizeb)//2

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

while not dA.is_empty_red():


print (dA.pop_red())
[100, None, None, None, None, None, None, None, None, None, 0, None, None,
None, None, None, None, None, None, None] 1 1

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

You might also like