CMU 15-112: Fundamentals of Programming and Computer Science Week9/10 Practice (Due never)

1. 5 Reasoning About (Recursive) Code problems
In just a few words of plain English, state what each of the following functions does in general. You will receive no credit for describing the line-by-line low-level behavior of the code. For example:
def mystery(list): count = 0 for value in list: if (value == 0): count += 1 return count
Correct answer: "This function returns the number of 0's in the given list." Or, if you prefer to be succinct, just: "number of 0's". Incorrect answer (no points): "This function sets count to 0, then sets value to each element in list in turn, and for each value, if it is 0, it adds one to count, and then returns count". This is all true, but completely misses the high-level behavior of the function, and so would receive zero points.

1. def f(n): # assume n is a non-negative integer if (n < 10): return 1 else: return 1 + f(n//10)

2. def f(a): # assume a is a list of strings if (len(a) == 0): return "" else: x = f(a[1:]) if (len(x) > len(a[0])): return x else: return a[0]

3. def f(a): # assume a is a list of integers if (len(a) == 0): return 0 elif (len(a) == 1): return (a[0] % 2) else: i = len(a)//2 return f(a[:i]) + f(a[i:])

4. def f(n): # assume n is a non-negative integer if (n == 0): return 1 else: return 2*f(n-1)

5. def f(n): # assume n is a non-negative integer if (n == 0): return 0 else: return f(n-1) + 2*n - 1 # Hint: you may want to try this function with a few sample values. # The answer should quickly become apparent, though you may wish to # think about why the answer is in fact what it is.

2. countFiles(path)
Write the recursive function countFiles(path), which takes a string specifying a path to a folder and returns the total number of files in that folder (and all its subfolders). Do not count folders (or subfolders) as files. Do not worry if the supplied path is not valid or is not a folder. To help test your code, here are some assertions for use with sampleFiles:
assert(countFiles("sampleFiles/folderB/folderF/folderG") == 0) assert(countFiles("sampleFiles/folderB/folderF") == 0) assert(countFiles("sampleFiles/folderB") == 2) assert(countFiles("sampleFiles/folderA/folderC") == 4) assert(countFiles("sampleFiles/folderA") == 6) assert(countFiles("sampleFiles") == 10)
Note: regarding efficiency, it is overtly inefficient to create a list of any kind in your solution to this problem. In particular, you may not do this:
def countFiles(path): return len(listFiles(path))

3. permutations
Write the recursive function permutations(a, k), which modifies the recursive permutations function from the course notes so that it returns all the permutations of length k from the elements in the list a, or the empty list [] if no such permutations exist. For example, permutations([1,2,3,4], 2) would return some ordering of this list:
[[1, 2], [1, 3], [1, 4], [2, 1], [2, 3], [2, 4], [3, 1], [3, 2], [3, 4], [4, 1], [4, 2], [4, 3]]
We say "some ordering" because your list must include the correct permutations, but in any order. And, once again, your solution must be recursive, and in fact it must be an obvious adaptation of the code in the course notes.

For your testing purposes, it might be helpful to use Pythons built-in permutations function. For example, this might be helpful (it generates the solution above):
import itertools a = [1,2,3,4] print [list(x) for x in itertools.permutations(a, 2)]
Hint: do not worry about the case where the original list contains duplicate values.

Note: regarding efficiency, your code should compute the 870-item result to permutations(range(30), 2) almost instantly, and the 117,600-item result to permutations(range(50),3) with at most a couple of seconds delay.

Most solutions that are not efficient enough will run much too long on these examples. The reason: generally, one way or another, they will create lots of "extra" permutations and then slice, pop, or remove their way down to the required size. For example, this modification of the permutation code is not acceptable:
def permutations(a, k): # inefficient version that uses permutations code from class, unmodified, # and then, assuming len(a)>=k, uses only first k elements of each permutation, # ignoring the rest (which is not ok). This also produces some duplicates, # so either have to remove all the duplicates at the end (which is also not ok), # or add an expensive O(n) check inside our loop to check if a permutation # is already in our list (which is also not ok). result = [ ] allPerms = allPermutations(a) for fullLengthPerm in allPerms: perm = fullLengthPerm[0:k] # not ok: essentially "removes" (n-k) elements if perm not in result: # also not ok result += [perm] print "len(allPerms) = ", len(allPerms) print "len(result) = ", len(result) return result def allPermutations(a): # renamed permutations code from class notes # returns a list of all permutations of the list a if (len(a) == 0): return [[]] else: allPerms = [ ] for subPermutation in allPermutations(a[1:]): for i in xrange(len(subPermutation)+1): allPerms += [subPermutation[:i] + [a[0]] + subPermutation[i:]] return allPerms
Why is this unacceptable? Well, say we are finding permutations(range(9), 2). The answer contains 72 permutations of two numbers each. However, the code above will generate all 362,880 permutations of length 9 to find these 72 permutations. This huge amount of extra work makes it intractable to use this code to compute permutations(range(30),2), let alone permutations(range(50),3). Try it, if you want to wait a very, very long time!

Big Hint: one way to solve this is to modify the permutations code from class to take a second parameter, k, and to return all the permutations of a that are no larger than length k. This still requires a non-recursive wrapper function (like above) that drops all the permutations that are not of length k, but since we never produce any permutations larger than length k, this is much, much faster than the code above.

4. fractalFlower
First, write the function simpleFlower that takes an integer number representing the size of the flower (and whatever else you need to do turtle graphics -- see the Koch Snowflake example for details). Your function will draw the following figure:

The position and orientation of the flower will depend on the initial position and orientation of the turtle. The origin of the flower is the bottom part of the stem, and its length develops straight ahead on the current direction of the turtle. When your function terminates, the turtle should be back to the same position and the same orientation it started.

Next, write the function fractalFlower that takes an integer representing the size of the flower, and an integer representing the maximum level (depth) of recursion, and again whatever else you need to do turtle graphics. Your function will paint a recursive fractal flower with the same basic shape outlined above, but with each petal recursively replaced by a scaled down version of the flower itself. For example, a fractal flower with a maximum recursion level of 4 will result in a picture like this one:

Your program should include some test code that draws three flowers, all of them facing up, on three different positions of the screen: (a) a simple flower of size 200; (b) a fractal flower of depth 3 and size 250; and (c) a fractal flower of depth 4 and size 300. Your three test flowers should not overlap.

5. vicsekFractals
Write the function vicsekFractals() that runs an animation that draws Vicsek fractals that fill a 400x400 window, where a Vicsek fractal is like so (where the depth is determined each time the user presses a digit key):

6. pythagoreanTree
First, read about the Pythagoras Tree here. With this in mind, write the function bonusPythagorasTree() that works like hFractal() only here it makes a Pythagoras Tree. For half-credit, just make the balanced one in the first row of the image. For full-credit, oscillate between sloped trees, as in the second image, but varying the angles back and forth over time so that there is a waving effect, as though blowing in the wind (well, with enough imagination).

7. twoStackHanoi
Background: Here, we will consider a modified form of the Towers of Hanoi problem. Given an input n>0, there are 2n discs of increasing size (instead of just n discs of increasing size). There are 3 poles as in the original problem (Pole 0, Pole 1, and Pole 2). We label the discs with numbers {1, 2, 3, ..., 2n}, where the label of a disc corresponds to its size. Initially, the odd-numbered discs (i.e. the discs {1, 3, 5, ..., 2n-1}) are on Pole 0, and the even-numbered discs (i.e. the discs {2, 4, 6, ..., 2n}) are on Pole 2. The goal is to get all the discs on Pole 1 (the middle pole).

With this in mind, write the function twoStackHanoi(n) that takes a positive int n and returns a list of the moves in order required to solve a 2-stack Hanoi problem as described above (so, to ultimately move the n discs from Pole 0 and the n other discs from Pole 2 all to Pole 1, while also maintaining the Hanoi rule that no disc can be placed on a smaller disc). A "move" will be represented as a tuple (x,y), where x and y are both in the set {0,1,2}, and means to move one disc from Pole x to Pole y.

8. findRTP(digits) [20 pts]
Background: A number n is a right-truncatable prime, or RTP, if every prefix of n (including n itself) are all prime. So, 593 is an RTP because 5, 59, and 593 are all prime. With this in mind, write the function findRTP(digits) that takes a positive int, digits, and returns the smallest RTP with that many digits, or None if no such number exists. To do this, you must use backtracking even if you could do it in some other way. At each step, try to add one more digit to the right of the number. Also, make sure you are only creating RTP's as you go. Note: even though findRTP(8) returns 23399339, it runs almost instantly because backtracking rules out most numbers without trying them, so it actually calls isPrime very few times.
Hint: you may need to use callWithLargeStack so your isPrime does not stack overflow.

9. pegSolitaire
First, read up on peg solitaire here, and then read this entire writeup carefully. Then, write a peg solitaire solver using backtracking. The boards will be given to the function as a string of periods, O's, and spaces, which represent holes, pegs, and invalid spaces respectively (see examples below). The result should be returned as a list of moves to be made in the form (fromRow, fromCol, toRow, toCol), which indicates which piece to pick up (the from piece) and where it goes (the to piece). Your function must give the answer in a reasonable amount of time, which is defined as 10 seconds.

Note that this problem is actually pretty difficult to do. This is because there are a lot of possible moves one can make at some stages in the puzzle (whereas in nQueens for example there are always only n possible moves). As such, we would grade on a rolling scale as such (examples of each case below).

1pt boards with 10 pegs
1pt boards with 14 pegs
1pt boards with 16 pegs
2pts boards with 32 pegs

Your basic backtracking will eventually get the correct answer for all situations, but it'd probably take many many years to get the solution to the 32 peg board. As such, you will have to be a bit smarter to solve the higher peg boards, and maybe even need more advanced AI techniques to get the 32 peg board.

Here are some boards to play with.
board10 = """\ ... .O. ..OO.O. .O...O. ..O.O.. O.O ... """ board14 = """\ ... OO. ..O.OO. OO..OO. .OOO..O .O. ... """ board16 = """\ ... OO. ..OO... ..OO.OO OOO..OO OO. .O. """ board32 = """\ OOO OOO OOOOOOO OOO.OOO OOOOOOO OOO OOO """

10. Gate class and Subclasses: OOP Practice
A logic gate is a physical device that creates the functional equivalent of a logic operation in code [and, or, not]. It takes some number of input values, such as two values for and (input1 and input2), and produces a single output value. Write the Gate classes required to make the following test function work properly.

def getLocalMethods(clss): import types # This is a helper function for the test function below. # It returns a sorted list of the names of the methods # defined in a class. result = [ ] for var in clss.__dict__: val = clss.__dict__[var] if (isinstance(val, types.FunctionType)): result.append(var) return sorted(result) def testGateClasses(): print("Testing Gate Classes... ", end="") # require methods be written in appropriate classes assert(getLocalMethods(Gate) == ['__init__', '__str__', 'numberOfInputs', 'setInput']) assert(getLocalMethods(AndGate) == ['getOutput']) assert(getLocalMethods(OrGate) == ['getOutput']) assert(getLocalMethods(NotGate) == ['getOutput', 'numberOfInputs']) # make a simple And gate and1 = AndGate() assert(type(and1) == AndGate) assert(isinstance(and1, Gate) == True) assert(and1.numberOfInputs() == 2) and1.setInput(0, True) and1.setInput(1, False) # Hint: to get the name of the class given an object obj, # you can do this: type(obj).__name__ # You might do this in the Gate.__str__ method... assert(str(and1) == "And(True,False)") assert(and1.getOutput() == False) and1.setInput(1, True) # now both inputs are True assert(and1.getOutput() == True) assert(str(and1) == "And(True,True)") # make a simple Or gate or1 = OrGate() assert(type(or1) == OrGate) assert(isinstance(or1, Gate) == True) assert(or1.numberOfInputs() == 2) or1.setInput(0, False) or1.setInput(1, False) assert(or1.getOutput() == False) assert(str(or1) == "Or(False,False)") or1.setInput(1, True) assert(or1.getOutput() == True) assert(str(or1) == "Or(False,True)") # make a simple Not gate not1 = NotGate() assert(type(not1) == NotGate) assert(isinstance(not1, Gate) == True) assert(not1.numberOfInputs() == 1) not1.setInput(0, False) assert(not1.getOutput() == True) assert(str(not1) == "Not(False)") not1.setInput(0, True) assert(not1.getOutput() == False) assert(str(not1) == "Not(True)") print("Passed!") testGateClasses()

11. Book class: OOP Practice
Write the Book class so that it passes testBookClass, and uses the OOP constructs we learned this week as appropriate.
def testBookClass(): print("Testing Book class...", end="") # A Book has a title, and author, and a number of pages. # It also has a current page, which always starts at 1. There is no page 0! book1 = Book("Harry Potter and the Sorcerer's Stone", "J. K. Rowling", 309) assert(str(book1) == "Book<Harry Potter and the Sorcerer's Stone by " + "J. K. Rowling: 309 pages, currently on page 1>") book2 = Book("Carnegie Mellon Motto", "Andrew Carnegie", 1) assert(str(book2) == "Book<Carnegie Mellon Motto by Andrew Carnegie: " + "1 page, currently on page 1>") # You can turn pages in a book. Turning a positive number of pages moves # forward; turning a negative number moves backwards. You can't move past # the first page going backwards or the last page going forwards book1.turnPage(4) # turning pages does not return assert(book1.getCurrentPage() == 5) book1.turnPage(-1) assert(book1.getCurrentPage() == 4) book1.turnPage(400) assert(book1.getCurrentPage() == 309) assert(str(book1) == "Book<Harry Potter and the Sorcerer's Stone by " + "J. K. Rowling: 309 pages, currently on page 309>") book2.turnPage(-1) assert(book2.getCurrentPage() == 1) book2.turnPage(1) assert(book2.getCurrentPage() == 1) # You can also put a bookmark on the current page. This lets you turn # back to it easily. The book starts out without a bookmark. book3 = Book("The Name of the Wind", "Patrick Rothfuss", 662) assert(str(book3) == "Book<The Name of the Wind by Patrick Rothfuss: " + \ "662 pages, currently on page 1>") assert(book3.getBookmarkedPage() == None) book3.turnPage(9) book3.placeBookmark() # does not return assert(book3.getBookmarkedPage() == 10) book3.turnPage(7) assert(book3.getBookmarkedPage() == 10) assert(book3.getCurrentPage() == 17) assert(str(book3) == "Book<The Name of the Wind by Patrick Rothfuss: " + \ "662 pages, currently on page 17, page 10 bookmarked>") book3.turnToBookmark() assert(book3.getCurrentPage() == 10) book3.removeBookmark() assert(book3.getBookmarkedPage() == None) book3.turnPage(25) assert(book3.getCurrentPage() == 35) book3.turnToBookmark() # if there's no bookmark, don't turn to a page assert(book3.getCurrentPage() == 35) assert(str(book3) == "Book<The Name of the Wind by Patrick Rothfuss: " + \ "662 pages, currently on page 35>") # Finally, you should be able to compare two books directly book5 = Book("A Game of Thrones", "George R.R. Martin", 807) book6 = Book("A Game of Thrones", "George R.R. Martin", 807) book7 = Book("A Natural History of Dragons", "Marie Brennan", 334) book8 = Book("A Game of Spoofs", "George R.R. Martin", 807) assert(book5 == book6) assert(book5 != book7) assert(book5 != book8) book5.turnPage(1) assert(book5 != book6) book5.turnPage(-1) assert(book5 == book6) book6.placeBookmark() assert(book5 != book6) print("Done!")