#! /usr/bin/python3 # ******************************************************** # CS 200 HW #4 DUE Monday, 10/30/2023 at 11:59 pm # via the submit system on the Zoo # ******************************************************** # Name: Larry Dunn # Email address: larry.dunn@yale.edu # ******************************************************** # This file may be opened in Python 3 # Lines beginning with hashes are comments. # If you are asked to write a procedure, please make sure it has # the specified name, and the specified number and order of arguments. # The names of the formal arguments need not be the same # as in the problem specification. # For each problem, the intended inputs to your procedures # are specified (for example, "positive integers") # and your procedures need not do anything reasonable # for other possible inputs. # You may write auxiliary procedures in addition to # the requested one(s) -- for each of your auxiliary # procedures, please include a comment explaining # what it does, and giving an example or two. # You may also use procedures you have written # elsewhere in this assignment or previous assignments. # They only need to be defined once somewhere within this file. # Reading: Learning Python, Chapters 26 - 32 # Reading: Python Cookbook, Chapter 8 # Algorithm Design Manual, Steven Skiena # https://link.springer.com/book/10.1007%2F978-1-84800-070-4 # ******************************************************** # ** problem 0 ** (1 easy point) # Replace the number 0 in the definition below to indicate # the number of hours you spent doing this assignment # Decimal numbers (eg, 6.237) are fine. Exclude time # spent reading. def hours(): return 6 # ******************************************************** # ** problem 00 ** (1 fairly easy point) # Below is a UNIX transcript with one command replaced by XXXX transcript = ''' bash-4.4$ pwd /home/accts/sbs5/cs200/hws/answers/x bash-4.4$ echo hello world > world bash-4.4$ xxxx bash-4.4$ ls -l total 0 -rw-rw-r-- 1 sbs5 cs200ta 12 Feb 10 15:46 world -rw-rw-r-- 1 sbs5 cs200ta 12 Feb 10 15:46 World bash-4.4$ diff world world bash-4.4$ diff world World 1c1 < hello world --- > Hello World ''' # define xxxx below to be the correct UNIX command. def xxxx(): return "echo Hello World > World" # ******************************************************** # In this assignment you will create a number of data structures: # ## A common use of classes is to implement data structures. ## This is an example of a stack, ## which is a LIFO - last in first out - structure. ## It is a collection. ## Items are added to the stack with push and removed with pop. ## ----------------------------------------------------------- ## We will see that the python virtual machine for interpreting ## byte code is based on a stack architecture. ## ----------------------------------------------------------- class stack: def __init__(self,stuff=[]): self.items = stuff[:] self.size = len(stuff) def __repr__(self): return "stack({})".format(list(self.items)) def isempty(self): return self.items == [] def push(self, item): self.items.append(item) self.size += 1 def peek(self): if self.isempty(): return "Error: stack is empty" else: return self.items[-1] def pop(self): if self.isempty(): return "Error: stack is empty" else: self.size -= 1 return self.items.pop() ## swap the top two items in the stack def rotate(self): if self.size < 2: return "Error: stack has fewer than 2 elements" else: self.items[-1], self.items[-2] = self.items[-2], self.items[-1] ## define the iterator for stack. Used in for or list comprehension def __iter__(self): """Return iterator for the stack.""" if self.isempty(): return None else: index = self.size -1 while index >= 0: yield self.items[index] index -= 1 ## overload equality operator. def __eq__(self, other): if type(other) != type(self): return False if self.items == other.items: return True else: return False # copy constructor - clone the current instance def copy(self): s = stack(self.items) return s ''' s = stack() s.push(1) s.push(2) s.push(3) s.push(4) ''' ## test the iterator def itest(s): for i in s: print (i) return [x for x in s] ## revstr uses a stack to reverse a string ## ---------------------------------------- def revstr(str): s = stack() for c in str: s.push(c) result = [] while (not s.isempty()): result.append(s.pop()) return ''.join(result) # ******************************************************** # ** problem 1 ** (8 points) # # Write a procedure balanced(string) that reads string, and determines # whether its parentheses are "balanced." Hint: for left delimiters, # push onto stack; for right delimiters, pop from stack and check # whether popped element matches right delimiter. def balanced(string): parentheses = stack([]) for n in string: if n == '(': parentheses.push(n) if n == ')': if parentheses.pop() != '(': return False if parentheses.items == []: return True else: return False # ******************************************************** # ** problem 2 ** (10 points) # ## Write a queue data structure, similar to the stack above. ## Whereas a stack is LIFO (last in first out), a queue is ## FIFO = first in, first out ## See Skiena, page 71 class queue: def __init__(self, stuff=[]): self.data = stuff[:] self.size = len(stuff) def __str__(self): return ("queue(" + str(self.data) + ")") def __repr__(self): return "stack({})".format(list(self.data)) # is the queue empty? true or false def isempty(self): return self.data == [] # add an item to queue def enqueue(self, item): self.data.append(item) self.size += 1 # remove next item. error message if queue is empty def dequeue(self): if self.isempty(): return "queue is empty" else: self.size -= 1 return self.data.pop(0) # return the next item without removing it. # return error message if queue is empty def peek(self): if self.isempty(): return "queue is empty" else: return self.data[0] ## define the iterator for queue. Used in for or list comprehension ## similar to iterator for stack. def __iter__(self): ## is it not just exactly the same as stack?? if self.isempty(): return None else: itersize = self.size -1 index = 0 while index <= itersize: yield self.data[index] index += 1 ## overload equality operator. def __eq__(self, other): if type(other) != type(self): return False if self.data == other.data: return True else: return False # copy constructor - clone the current instance def copy(self): q = queue(self.data) return q # ******************************************************** # ** problem 3 ** (10 points) ## create a queue using two stacks: s1 and s2 ## enqueue() pushes items on s1 ## dequeue() pop's s2, unless s2 is empty, in which case ## keep popping s1 onto s2 until s1 is empty. Then pop s2 ## peek is similar to dequeue, except no final pop class queue2: # initialize stacks def __init__(self, stuff1 = [], stuff2 = []): self.s1 = stack(stuff1[:]) self.s2 = stack(stuff2[:]) def __str__(self): return ("queue2(" + str(self.s1) + ', ' + str(self.s2) + ")") def __repr__(self): self.peek() return "stack({})".format(list(self.s2.items)) # is the queue empty? true or false def isempty(self): return self.s1.isempty() and self.s2.isempty() # add an item to queue def enqueue(self, item): self.s1.push(item) # remove next item. error message if queue is empty def dequeue(self): if self.isempty(): return "queue is empty" if self.s2.isempty(): while not self.s1.isempty(): self.s2.push(self.s2.pop()) return self.s2.pop() # return the next item without removing it. # return error message if queue is empty def peek(self): if self.isempty(): return "queue is empty" if self.s2.isempty(): while not self.s1.isempty(): self.s2.push(self.s1.pop()) return self.s2.items[(self.s2.size -1)] ## define the iterator for queue2. Used in for or list comprehension ## HINT: ## convert stacks to lists. ## extend the stack 2 list with the reverse of the stack 1 list ## use a for loop to iterate through the extended list, yielding the item def __iter__(self): self.peek() s2list = [] for n in self.s2: s2list.append(n) if self.isempty(): return None else: itersize = len(s2list) -1 #index = 0 for n in s2list: yield n #index += 1 ## overload equality operator ## true if both stacks are respectively equal ## use the convert stacks to list method given above for __iter__ def __eq__(self, other): if (self.s1 == other.s1) & (self.s2 == other.s2): return True else: return False # copy constructor - clone the current instance def copy(self): q = queue2(self.s1.items, self.s2.items) return q # ******************************************************** # ** problem 4 ** (10 points) # Write a procedure to reverse a queue. # It should work with either q implementation. def reverseq(q): revstack = stack([]) qlen = 0 for n in q: qlen = qlen+1 for n in range(qlen): revstack.push(q.dequeue()) for n in revstack: q.enqueue(n) return q # ******************************************************** # ** problem 5 ** (20 points) ## Reading: Skiena pages 89-93 # Create a hash table. # It will be a list of size buckets. # Each bucket will itself contain a list. # If two items fall in the same bucket, # the respective list will contain both items. # See Skiena page 89 class myhash: def __init__(self, size = 20): self.size = size self.buckets = [] self.count = 0 for n in range (size): self.buckets.append([]) def __repr__(self): contents = [] for n in self.buckets: if n: contents.append(n) return ("hashtable(" + str(contents) + ")") def __str__(self): contents = [] for n in self.buckets: if n: contents.append(n) return ("hashtable(" + str(contents) + ")") # is the hash table empty? def isempty(self): if self.count == 0: return True else: return False # add an item with the given key and value # if there is already an item with the given key, remove it. # no duplicate keys def put(self, key, value): index = self.hashfun(key) bucket = self.buckets[index] #delete the pair if key if already there for i, n in enumerate(bucket): if key in n: del bucket[i] self.count -= 1 return bucket.append((key,value)) self.count += 1 # Retrieve the value for the given key. def get(self,key): index = self.hashfun(key) bucket = self.buckets[index] for i, n in enumerate(bucket): if key in n: return bucket[i][1] return False # Remove the item for the given key. def remove(self,key): index = self.hashfun(key) bucket = self.buckets[index] for i, n in enumerate(bucket): if key in n: del bucket[i] self.count -= 1 return ## Create a hash function using the djb2 algorithm. ## http://www.cse.yorku.ca/~oz/hash.html ## If the optional debug parameter is true, ## print out the value of the hash. def hashfun(self,key, debug=False): hashvalue = 5381 for n in key: hashvalue = ((hashvalue << 5) + hashvalue) + ord(n) if debug == True: return ("Hash of: " + str(key) + " = " + str(hashvalue) + " ==> " + str(hashvalue % len(self.buckets))) return hashvalue % len(self.buckets) ## Iterate through the buckets and their respective contents. def __iter__(self): if self.isempty(): return None for n in self.buckets: if n: yield n[0][0] ## overload equality operator def __eq__(self, other): if (self.buckets == other.buckets): return True else: return False # copy constructor - clone the current instance def copy(self): h = myhash() h.size = self.size h.count = self.count h.buckets = self.buckets return h ''' h = myhash(20) h.put("one",1) h.put("two",2) h.put("three",3) ''' # ******************************************************** # ** problem 6 ** (10 points) # Use your hash function to implement remove duplicates for strings. def removedups(string): strhash = myhash(len(string)) for n in string: strhash.put(n, 0) newstring = "" for n in strhash: newstring = newstring + n newstring = ''.join(sorted(newstring)) return newstring # ******************************************************** # ** problem 7 ** (20 points) ## Reading: Skiena pages 109-115 # Implement a heap per the description in Skiena. class heap: def __init__(self, size = 10): self.size = size self.count = 1 self.data = [] for n in range (size + 1): self.data.append(0) def __str__(self): return 'heap( ' + str(self.data) + " )" def __repr__(self): return 'heap( ' + str(self.data) + " )" def isempty(self): if not self.data: return False else: return True # Add a new element to the heap and adjust as needed. def insert(self,item): index = self.count self.data[index] = item self.count += 1 #if (self.tree[heap.parent(index)] > self.tree(index)): self.bubbleup(index) # This could be tricky. I am giving it to you. def bubbleup(self, n): if heap.parent(n) == -1: return if self.data[heap.parent(n)] > self.data[n]: self.data[n],self.data[heap.parent(n)] = self.data[heap.parent(n)],self.data[n] self.bubbleup(heap.parent(n)) # Remove the smallest element and adjust the heap. def extractmin(self): min = self.data[1] self.data[1] = self.data[self.count-1] self.data[self.count] = 0 self.count -= 1 #replace with last element and bubble that element down self.bubbleDown(1) return min # This could be tricky. I am giving it to you. def bubbleDown(self,p): c = self.child(p) min_index = p for i in [0, 1]: if ((c + i) <= self.count): if self.data[min_index] > self.data[c + i]: min_index = c+i if min_index != p: self.data[p], self.data[min_index] = self.data[min_index], self.data[p] self.bubbleDown(min_index) @staticmethod def parent(n): if (n == 1): return (-1) else: return int(n/2) @staticmethod def child(n): return (2 * n) ## Define the iterator for heap. Used in for or list comprehension. def __iter__(self): for x, n in enumerate(self.data): if x > 0: yield n if x + 1 >= self.count: break ## overload equality operator def __eq__(self, other): if self.data == other.data: return True else: return False # copy constructor - clone the current instance def copy(self): h = heap() h.size = self.size h.count = self.count h.data = self.data return h ''' hh = heap(10) hh.insert(12) hh.insert(4) hh.insert(8) ''' # ******************************************************** # ** problem 8 ** (10 points) # Write a function that takes in a list of positive integers of # size n and returns a sorted list containing the n/2 smallest elements # use a heap def smallest(lst = [4,2,5,6,8,11,99,6,77]): smallheap = heap(len(lst)+1) for n in lst: smallheap.insert(n) if not smallheap.data: return [] smallerlen = len(lst) // 2 shortlist = [] for n in range (smallerlen): shortlist.append(smallheap.extractmin()) #shortlist = shortlist[:smallerlen] return shortlist # ******************************************************** # ******************************************************** ### test function from google python course ### ======================================= # Provided simple test() function used in main() to print # what each function returns vs. what it's supposed to return. def test(got, expected): if (hasattr(expected, '__call__')): OK = expected(got) else: OK = (got == expected) if OK: prefix = ' OK ' else: prefix = ' X ' print ('%s got: %s expected: %s' % (prefix, repr(got), repr(expected))) # Provided main() calls the above functions with interesting inputs, # using test() to check if each result is correct or not. def main(): print ('hours') print('# is it greater than 0?') test(hours(), lambda x: x > 0) print ('stack') s = stack() s.push(1) s.push(2) s.push(3) s.push(4) test(s,stack([1, 2, 3, 4])) test(s == s.copy(), True) test([x for x in s], [4,3,2,1]) test(s.peek(),4) test(3 in s, True) test(5 in s, False) test(s.pop(),4) test(s.pop(),3) test(s.peek(),2) test(revstr('abcdef'), 'fedcba') test(revstr(''), '') print ('balanced') test(balanced('dkdk'),True) test(balanced('()()()()'),True) test(balanced('()()()())'),False) test(balanced('()()()()(('),False) test(balanced('(a)s(d)f(g)gh(h)j(k'),False) print ('queue') d = queue() d.enqueue(9) d.enqueue(1) d.enqueue(2) test(d == d.copy(), True) test(d.peek(),9) test(d.data,[9, 1, 2]) test([x for x in d], [9,1,2]) test(2 in d, True) test(5 in d, False) test(d.dequeue(),9) test(d.dequeue(),1) test(d.dequeue(),2) test(2 in d, False) test(d.dequeue(),'queue is empty') print ('\nqueue 2 stacks') d2 = queue2() d2.enqueue(9) d2.enqueue(1) d2.enqueue(2) test(d2 == d2.copy(), True) test(d2 == queue2([9,1,2], []),True) test(d2 == queue2([1,2], []),False) test(d2.peek(),9) test([x for x in d2], [9,1,2]) test(2 in d2, True) test(5 in d2, False) test(d2.dequeue(),9) test(d2.dequeue(),1) test(d2.dequeue(),2) test(2 in d2, False) test(d2.dequeue(),'queue is empty') print ('\nreverseq') q = queue() q.enqueue(1) q.enqueue(2) q.enqueue(3) q.enqueue(4) test(str(reverseq(q)),'queue([4, 3, 2, 1])') q2 = queue2() q2.enqueue(1) q2.enqueue(2) q2.enqueue(3) q2.enqueue(4) test(str(reverseq(q2)),'queue2(stack([4, 3, 2, 1]), stack([]))') print ('\nmyhash') h = myhash(20) h.put("one",1) h.put("two",2) h.put("three",3) h2 = myhash(20) test(h == h.copy(), True) h2.put("one",1) h2.put("two",2) h2.put("three",3) test(h == h2, True) h2.put("four",4) test(h == h2, False) test("one" in h, True) test("zero" in h, False) test([x for x in h],['one', 'three', 'two']) test(h.hashfun("one"),7) test(h.hashfun("two"),19) test(h.hashfun("three"),17) test(h.get("three"),3) test(h.get("four"),False) test(h.remove("three"),None) test(h.get("three"),False) test(h.hashfun('one',True),'Hash of: one = 193501607 ==> 7') test(h.hashfun('two',True),'Hash of: two = 193507359 ==> 19') print ('\nremovedups') test(removedups('abcabcabc'),'abc') test(removedups('abcabcabcefg'), 'abcefg') print ('\nheap') hh = heap(10) hh.insert(12) hh.insert(4) hh.insert(8) test(hh == hh.copy(), True) hh2 = heap(10) hh2.insert(12) hh2.insert(4) hh2.insert(8) test(hh == hh2, True) hh2.insert(80) test(hh == hh2, False) test(str(hh),'heap( [0, 4, 12, 8, 0, 0, 0, 0, 0, 0, 0] )') test(4 in hh, True) test(44 in hh, False) test(0 in hh, False) test([x for x in hh], [4,12,8]) test(hh.child(1),2) test(hh.child(2),4) test(hh.parent(3),1) test(hh.extractmin(),4) test(hh.extractmin(),8) print ('\nsmallest') test(smallest(),[2, 4, 5, 6]) test(smallest([]),[]) test(smallest([1]),[]) test(smallest([1,2]),[1]) test(smallest([3,4,5,6,2,3,4,5,6,7,88,22,11,33,22,44]),[2, 3, 3, 4, 4, 5, 5, 6]) if __name__ == '__main__': main()