CMU 15-112: Fundamentals of Programming and Computer Science
Homework 3 (Due Wed 13-July at 8pm ET (Pittsburgh-time))
- As usual, this homework is solo. You must not collaborate with anyone (besides current course TA's and faculty) in any way. See the syllabus for more details.
- To start:
- Create a folder named 'hw3'
- Download all of these to that folder:
- Edit hw3.py using VSCode
- When you have completed and fully tested hw3, submit hw3.py to Autolab. For this hw, you may submit up to 7 times, but only your last submission counts.
- Do not use lists, list indexing, sets, dictionaries, or recursion this week.
- Do not hardcode the test cases in your solutions.
Homework3 overview:
- largestNumber [10 pts]
- rotateStringLeft [10 pts]
- isRotation [15 pts]
- longestSubpalindrome [20 pts]
- patternedMessage [20 pt]
- mastermindScore [25 pts]
- Bonus/Optional [up to 4 bonus pts]
These are bonus/optional. They are fun, interesting problems that are worth very few points. If you attempt these, do them for the fun and the learning, not the points. Aside from writeup clarifications, we will not provide hints or debugging help on these or future bonus problems!- Right-Left Route Ciphers (encode + decode) [2 pts]
- topLevelFunctionNames [1 pt]
- getEvalSteps [1 pt]
- largestNumber [10 pts]
Write the function largestNumber(s) that takes a string of text and returns the largest int value that occurs within that text, or None if no such value occurs. You may assume that the only numbers in the text are non-negative integers and that numbers are always composed of consecutive digits (without commas, for example). For example:largestNumber("I saw 3 dogs, 17 cats, and 14 cows!")
returns 17 (the int value 17, not the string "17"). AndlargestNumber("One person ate two hot dogs!")
returns None (the value None, not the string "None"). - rotateStringLeft [10 pts]
Note: To receive credit, do not use loops on this problem.
Write the function rotateStringLeft(s, n) that takes a string s and a possibly-negative integer n. If n is non-negative, the function returns the string s rotated n places to the left. If n is negative, the function returns the string s rotated |n| places to the right. So, for example:assert(rotateStringLeft('abcd', 1) == 'bcda') assert(rotateStringLeft('abcd', -1) == 'dabc') - isRotation [15 pts]
Write the function isRotation(s, t) that takes two possibly-empty strings and returns True if one is a rotation of the other. Note that a string is not considered a rotation of itself. Hint: The previous problem may be helpful here. - longestSubpalindrome [20 pts]
Write the function longestSubpalindrome(s), that takes a string s and returns the longest palindrome that occurs as consecutive characters (not just letters, but any characters) in s. So:longestSubpalindrome("ab-4-be!!!")returns "b-4-b". If there is a tie, return the lexicographically larger value -- in Python, a string s1 is lexicographically greater than a string s2 if (s1 > s2). So:longestSubpalindrome("abcbce")returns "cbc", since ("cbc" > "bcb"). Note that unlike the previous functions, this function is case-sensitive (so "A" is not treated the same as "a" here). Also, from the explanation above, we see that longestSubpalindrome("aba") is "aba", and longestSubpalindrome("a") is "a". - patternedMessage [20 pt]
Write the function patternedMessage(message, pattern) that takes two strings, a message and a pattern, and returns a string produced by replacing the non-whitespace characters in the pattern with the non-whitespace characters in the message. As a first example:
call result patternedMessage("Go Pirates!!!", """ *************** ****** ****** *************** """)
GoPirates!!!GoP irates !!!GoP irates!!!GoPira
Here, the message is "Go Pirates!!!" and the pattern is a block of asterisks with a few missing in the middle. Notice how the whitespace in the pattern is preserved, but the whitespace in the message is removed. Also, note that any leading or trailing newlines in the pattern are removed.
Here is another example:
call result patternedMessage("Three Diamonds!",""" * * * *** *** *** ***** ***** ***** *** *** *** * * * """)
T h r eeD iam ond s!Thr eeDia monds !Th ree Dia m o n
Hint: While you may solve this how you wish, our sample solution did not use replace in any way. Instead, we started with the empy string, and built up the result character by character. How did we determine the next character? Using both the message and the pattern in some way...
Here are two more straightforward examples:
assert(patternedMessage("abc def", "***** ***** ****") == "abcde fabcd efab") assert(patternedMessage("abc def", "\n***** ***** ****\n") == "abcde fabcd efab")
And here is one last example, just for fun:
patternedMessage("Go Steelers!", """ oooo$$$$$$$$$$$$oooo oo$$$$$$$$$$$$$$$$$$$$$$$$o oo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o o$ $$ o$ o $ oo o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o $$ $$ $$o$ oo $ $ '$ o$$$$$$$$$ $$$$$$$$$$$$$ $$$$$$$$$o $$$o$$o$ '$$$$$$o$ o$$$$$$$$$ $$$$$$$$$$$ $$$$$$$$$$o $$$$$$$$ $$$$$$$ $$$$$$$$$$$ $$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$ $$$$$$$$$$$$$$ '$$$ '$$$'$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ '$$$ $$$ o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ '$$$o o$$' $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$o $$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$' '$$$$$$ooooo$$$$o o$$$oooo$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ o$$$$$$$$$$$$$$$$$ $$$$$$$$'$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$' '''' $$$$ '$$$$$$$$$$$$$$$$$$$$$$$$$$$$' o$$$ '$$$o '$$$$$$$$$$$$$$$$$$'$$' $$$ $$$o '$$'$$$$$$' o$$$ $$$$o o$$$' '$$$$o o$$$$$$o'$$$$o o$$$$ '$$$$$oo '$$$$o$$$$$o o$$$$' '$$$$$oooo '$$$o$$$$$$$$$' '$$$$$$$oo $$$$$$$$$$ '$$$$$$$$$$$ $$$$$$$$$$$$ $$$$$$$$$$' '$$$' """)
Returns this:
GoSteelers!GoSteeler s!GoSteelers!GoSteelers!GoS teelers!GoSteelers!GoSteelers!GoS te el er s ! Go Steelers!GoSteelers!GoSteelers!GoSteel er s! GoSt ee l e rs !GoSteeler s!GoSteelers! GoSteelers !GoSteel ers!GoSte elers!GoSt eelers!GoSt eelers!GoSt eelers!G oSteele rs!GoSteele rs!GoSteele rs!GoSteelers!GoSteeler s!GoSteelers!GoSteelers !GoSteelers!G oSteelers!GoSt eele rs!GoSteelers!GoSteelers!GoSteelers!GoSteelers!GoSteel ers! GoS teelers!GoSteelers!GoSteelers!GoSteelers!GoSteelers !GoSt eele rs!GoSteelers!GoSteelers!GoSteelers!GoSteelers!GoSt eele rs! GoSteelers!GoSteelers!GoSteelers!GoSteelers!Go Steelers!GoSteele rs!GoSteelers !GoSteelers!GoSteelers!GoSteelers!GoS teelers!GoSteelers !GoSteelers!G oSteelers!GoSteelers!GoSteelers!Go Steel ers! GoSt eelers!GoSteelers!GoSteelers!G oSte elers !GoSteelers!GoSteelers! GoS teel ers!GoSteel ers! GoSte elers !GoSte elers!GoSteele rs!Go Steelers !GoSteelers! GoStee lers!GoSte elers!GoSteeler s!GoSteele rs!GoSteel ers!GoSteele rs!GoSteeler s!GoSteeler s!GoS
Hint: You will almost surely want to print strings to help you debug here, but whitespace can be quite tricky in this problem. So... Instead of usingprint(s)
in your debugging, useprint(repr(s))
. That way, you can easily see the whitespace. This can make a huge difference in how long this problem takes! We highly recommend using this advice. - mastermindScore [25 pts]
This problem is inspired by the game of Mastermind. We will slightly adapt the game, and then we will not play the entire game, but rather just compute the score for a single guess. For that, we will be given a target string of lowercase letters, like say 'ccba', and then a guess string of the same length and also of lowercase letters, like say 'ddbc'. We have to compute two different values: the number of characters in the guess that are an exact match -- that is, the same character as the target in the same location. And then we also have to compute the number of characters in the guess that are a partial match -- the right character, but not in the right location. In the example, with a target of 'ccba' and a guess of 'ddbc', 'b' is an exact match, and 'c' is a partial match.
Your function should return a string describing the matches, with the exact match count first followed by the partial match count. In the example above, we get:assert(mastermindScore('ccba', 'ddbc') == '1 exact match, 1 partial match')If there are multiple matches, report them like so:assert(mastermindScore('abcd', 'aabd') == '2 exact matches, 1 partial match')If there is only one kind of match -- exact or partial -- then leave off the other kind from the report, like so:assert(mastermindScore('efgh', 'abef') == '2 partial matches') assert(mastermindScore('efgh', 'efef') == '2 exact matches')If there are no matches at all, return 'No matches', like so:assert(mastermindScore('ijkl', 'mnop') == 'No matches')Finally, if the two strings are in fact equal, then return 'You win!!!' like so:assert(mastermindScore('wxyz', 'wxyz') == 'You win!!!')Hint: Remember not to include any exactly-matched characters in your computation for partially-matched characters!
Bonus Problems
Bonus problems are entirely optional and worth few points. Do them for the fun and the learning. Aside from writeup clarifications, we will not provide hints or debugging help on these or future bonus problems!
- Right-Left Route Ciphers (encode + decode) [bonus, 2 pts]
Background: A right-left route cipher is a fairly simple way to encrypt a message. It takes two values, some plaintext and a number of rows, and it first constructs a grid with that number of rows and the minimum number of columns required, writing the message in successive columns. For example, if the message is WEATTACKATDAWN, with 4 rows, the grid would be:W T A W E A T N A C D T K A
We will assume the message only contains uppercase letters. We'll fill in the missing grid entries with lowercase letters starting from z and going in reverse (wrapping around if necessary), so we have:W T A W E A T N A C D z T K A y
Next, we encrypt the text by reading alternating rows first to the right ("WTAW"), then to the left ("NTAE"), then back to the right ("ACDz"), and back to the left ("yAKT"), until we finish all rows. We precede these values with the number of rows itself in the string. So the encrypted value for the message WEATTACKATDAWN with 4 rows is "4WTAWNTAEACDzyAKT".
With this in mind, write the function encodeRightLeftRouteCipher that takes an all-uppercase message and a positive integer number of rows, and returns the encoding as just described.
Here are a few more examples to consider:
assert(encodeRightLeftRouteCipher("WEATTACKATDAWN",4) == "4WTAWNTAEACDzyAKT") assert(encodeRightLeftRouteCipher("WEATTACKATDAWN",3) == "3WTCTWNDKTEAAAAz") assert(encodeRightLeftRouteCipher("WEATTACKATDAWN",5) == "5WADACEAKWNATTTz")Be sure to take the time to fully understand each of those examples!
Hint: the grid described above is only conceptual. Your code will never actually construct a 2-dimensional grid (especially as you may not yet use lists!). Instead, you should use a clever scheme of indexing the message string where you translate a row and column into a single index into the message string.
More complete hint: let's do this example in a bit more detail, and we'll even provide an idea or two on how to simplify solving this:assert(encodeRightLeftRouteCipher("WEATTACKATDAWN",3) == "3WTCTWNDKTEAAAAz")
- Find the dimensions of the conceptual 2d grid
Since len('WEATTACKATDAWN') is 14, and we have 3 rows, we need math.ceil(14/3) or 5 columns. - Pad the string
We need 3*5, or 15 letters. We have 14. We have to add one. So we now have 'WEATTACKATDAWNz' - Imagine the conceptual 2d grid
We do not create this part. We just imagine it. But this is the 2d grid we imagine:W T C T W E T K D N A A A A z
- Label your rows and cols
To be sure we are visualizing the grid properly, let's add row and col labels, like so:col0 col1 col2 col3 col4 row0: W T C T W row1: E T K D N row2: A A A A z
- Label the padded string with row, col, and i
Let's use these row and col labels, but write them over the padded string (instead of the conceptual 2d grid). We'll also include the index i, like so:row: 0 1 2 0 1 2 0 1 2 0 1 2 0 1 2 col: 0 0 0 1 1 1 2 2 2 3 3 3 4 4 4 i: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 W E A T T A C K A T D A W N z
- Find a function f(row,col) --> i
Look at the patterns in the row, col, and i in the table we just made. See if you can find a function f(row, col) that takes any row and col (in the conceptual 2d grid) and returns the corresponding index i (in the padded string). Also, name this function something better than f.- Hint: from the table above, we see that the K is
in row 1 and column 2, and the K is at index 7
in the padded string, so...
f(1,2) == 7
- Hint: see how the row in the table above repeats: 0, 1, 2, 0, 1, 2,... What does this have to do with the fact that we have 3 total rows?
- Hint: from the table above, we see that the K is
in row 1 and column 2, and the K is at index 7
in the padded string, so...
- Now, traverse the 2d grid top-to-bottom, left-to-right
This step is not required, but it is super helpful. As only a temporary measure, we will solve a slightly easier version of the problem: we will simply ignore that every other row goes right-to-left. We'll make every row go left-to-right just for now. So use two loops, one going over every row, and inside that, one going over every column. For each row,col pair, use your function f() that you just wrote (and renamed) to find the index in the padded string. Remember that this was the conceptual grid:WTCTW ETKDN AAAAz
And so, when you are done with this step, you should have a string like this (which, again, is not the real solution, since we always go left-to-right):WTCTWETKDNAAAAz
- Now alternate left-to-right and right-to-left
Now make every-other-row go the other way. So the second row will change from ETKDN to NDKTE, like so:WTCTWNDKTEAAAAz
- Add the rows as a prefix
Easy enough:3WTCTWNDKTEAAAAz
- Return that string
We are done. To remind ourselves, here was the test case:assert(encodeRightLeftRouteCipher("WEATTACKATDAWN",3) == "3WTCTWNDKTEAAAAz")
Whew! And now that you have written the encoder, we need to write the decoder. For that, write the function decodeRightLeftRouteCipher, which takes an encoding from the previous problem and runs it in reverse, returning the plaintext that generated the encoding. For example, decodeRightLeftRouteCipher("4WTAWNTAEACDzyAKT") returns "WEATTACKATDAWN". - Find the dimensions of the conceptual 2d grid
- topLevelFunctionNames [bonus, 1 pt]
Write the function topLevelFunctionNames(code) that takes a possibly-multi-line string of Python code, and returns a string with the names of the top-level functions in that code, separated by dots ("."), in the order they appear in the code.
You may assume that the code is syntactically correct, with no non-ascii or non-printable characters in it. You may further assume that every top-level function is defined with a "def" statement, where the "def" is left-flush (so no spaces before the "def"), followed by exactly one space (" "), followed by the function name, followed by no spaces, followed by the open parenthesis for the parameters.
This task is complicated by the fact that there can be multi-line strings in the code, and a "def" inside a multi-line string is not a function definition, and should be ignored.
This is further complicated by the fact that there can be comments (#) in the code, and everything after the comment on that line should be ignored, including any potential string delimiters.
Also, note that comment characters (# or triple-quotes) can appear inside strings, in which case they are not the start of comments, and should be ignored.
Here is a sample test case for you:# f is redefined code = """\ def f(x): return x+42 def g(x): return x+f(x) def f(x): return x-42 """ assert(topLevelFunctionNames(code) == "f.g")
And here is another one:# g() is in triple-quotes (""") code = '''\ def f(): return """ def g(): pass""" ''' assert(topLevelFunctionNames(code) == "f")
- getEvalSteps [bonus, 1 pt]
Write the function getEvalSteps(expr), that takes a string containing a simple arithmetic expression, and returns a multi-line string containing the step-by-step (one operator at a time) evaluation of that expression. For example, this call:getEvalSteps("2+3*4-8**3%3")
produces this result (which is a single multi-line string):2+3*4-8**3%3 = 2+3*4-512%3 = 2+12-512%3 = 2+12-2 = 14-2 = 12
Here are some considerations and hints:- You are only responsible for legal input as described below. Numbers are limited to non-negative integers.
- Operators are limited to +, -, *, /, //, %, and **.
- All operators are binary, so they take two operands. So there are no unary operators, meaning "-5" is not a legal input. For that, you'd need "0-5".
- In fact, the previous restriction is even stronger: no intermediate value of expr can be negative! So "1-2+3" is not legal, since it would be converted first into "-1+3" which is not legal. So you can safely ignore all such cases.
- There are no parentheses.
- Operators must be evaluated by precedence (** first, then *,/,//,%, then +,-).
- Equal-precedence operators are evaluated left-to-right.
- Evaluation proceeds until a single integer with no operators remains after the equals sign.
- The equal signs must all be stacked directly over each other.
- You may write this however you wish, though you may want to write a helper function that finds the next operator to be applied, and another helper function that just applies a single operator. Something like that. In any case, top-down design is crucial here. And don't forget to thoroughly test your helper functions before using them!
- In our sample solution, we used very few string methods, just "find" and "isdigit". You may use others, but you should not spin your wheels trying to find that awesome string method that will make this problem remarkably easier, as that method does not exist.
- For this function, as any other function, you may not use the eval function, so you will have to write your own equivalent just for the kinds of simple expressions you will deal with here. Eval is dangerous and should be avoided, as it can lead to serious bugs or security holes.