For this hw, you may submit up to 7 times, but only your last submission counts.
Do not use strings (except where required in playPig and the bonus
problems), string indexing (even in bonus problems),
lists, list indexing, or recursion this week.
Do not hardcode the test cases in your solutions.
digitCount(n) [5 pts]
Write the function digitCount(n) that takes a possibly-negative int and returns the number of digits in it. So, digitCount(12323) returns 5, digitCount(0) returns 1, and digitCount(-111) returns 3. One way you could do this would be to return len(str(abs(n))), but you cannot do that, since you may not use strings here! This can be solved with logarithms, but seeing as this is "loops week", you should instead simply repeatedly remove the ones digit until you cannot.
hasConsecutiveDigits(n) [5 pts]
Write the function hasConsecutiveDigits(n) that takes a possibly-negative int value n and returns True if that number contains two consecutive digits that are the same, and False otherwise.
isPalindromicNumber(n) [5 pts]
Write the function isPalindromicNumber(n) that takes a non-negative
int n and returns True if that number is palindromic and
False otherwise, where a palindromic number is the same forwards
as backwards. For example, these numbers are palindromic: 0, 1,
99, 12321, 123321, and these numbers are not: 1211, 112, 10010.
nthPalindromicPrime(n) [10 pts]
Write the function nthPalindromicPrime(n) that takes a non-negative int n and returns the nth palindromic prime, where a palindromic prime is a number that is both prime and a palindrome. The first ten palindromic primes are 2, 3, 5, 7, 11, 101, 131, 151, 181, 191, so nthPalindromicPrime(0) would return 2, nthPalindromicPrime(1) would return 3, and so on.
Note: It is a good idea to write an isPrime(n) helper function. It's fine if yours is similar (or even identical) to the one in the course notes, but you will likely be required to write it from scratch on quizzes, so it is better to practice now instead of simply copying it. (If you do copy anything from the course notes, you should cite it with a simple comment like "#isPrime copied from https://www.cs.cmu.edu/~112/notes/notes-loops.html" right above the relevant code.)
mostFrequentDigit(n) [10 pts]
Write the function mostFrequentDigit(n), that takes a possibly-negative integer n and returns the digit from 0 to 9 that occurs most frequently in it, with ties going to the smaller digit.
findZeroWithBisection(f, x0, x1, epsilon) [15 pts]
As we will cover more carefully later in the course, a function may
take another function as an argument. For example, consider this code:
defh(n):return n+5deff(g, x):return2*g(x)
print(f(h,3)) # prints 16
Here, we define a function f whose first argument is another function. On the last line, we call f providing the function h, which is bound in f to the parameter g. Hence, calls to g in f are really calls to h. Play around
with the sample code to get the hang of this idea.
In mathematics, one way to numerically (as opposed to algebraically) find a
zero of a function f(x) is to search for the zero in between two points, using smaller steps as we get closer. (This is similar to binary search, which we will learn about in a later week.)
To start, we need to know two values, x0 and x1, with x0<x1, where f(x0) and f(x1)
have different signs (so one is positive and the other is negative). We know there is some value x in the
range [x0,x1] such that f(x)=0. (This is described by the Intermediate Value Theorem, if you're curious.)
Our goal is to find the x where f(x) == 0. First, we evaluate f(xmid), which is the midpoint between x0 and x1. If f(xmid) is exactly 0, we are done! Otherwise, we can divide our range in
half as such: if f(xmid) and f(x0) are the same sign, use the range [xmid,
x1]. Otherwise, f(xmid) and f(x1) must share the same sign, so use the
range [x0, xmid]. We repeat the process of evaluating f at the midpoint of our new range, then choosing a new range, and so forth until x0 and x1 are very close (i.e. within some suitably small epsilon).
With this in mind, write the function findZeroWithBisection that takes a
function f, a float x0, a float x1, and a float epsilon, and returns an
approximate value x in [x0,x1] where f(x) is approximately zero. Your
function should stop when x0 and x1 are within epsilon, and at that time
should return the midpoint of that range. Note that if it is not true that
exactly one of f(x0) and f(x1) is negative, your function should return the
Python value None, signifying that the bisection method cannot be used on
the given range.
Let's see this in action! First, we'll use it to approximate the square
root of 2 without the ** operator:
print("Use bisection to approximate sqrt(2):")
deff1(x):return x*x - 2# root at x=sqrt(2)
x = findZeroWithBisection(f1, 0, 2, 0.000000001)
print(f" x = {x}") # prints x = 1.41421356192
print(f" check: x**2 = {x*x}") # prints check: x**2 = 1.99999999871 (really close!)
Next, let's use it to approximate phi (the
golden ratio), without using the closed-form solution ((1 + sqrt(5))/2),
instead using the interesting property of phi that adding one to it is the
same as squaring it. That is, ((phi + 1) == phi**2). How do use that?
We convert it into an equation equal to 0: phi**2 - (phi + 1) == 0.
Now we're set! (Of course, we could just use the Quadratic Formula here,
but it's more interesting to use bisection, now that we have it!).
print("use bisection to approximate phi (the golden ratio):")
deff2(x):return x**2 - (x + 1) # root at x=phi
x = findZeroWithBisection(f2, 0, 2, 0.000000001)
print(f" x = {x}") # prints x = 1.61803398887
phi = (1 + 5**0.5)/2# the actual value (to within Python's floating point accuracy)
print(f" check: x/phi = {x/phi}") # prints check: check: x/phi = 1.00000000007 (nice!)
The previous two examples are nice, but simpler techniques than bisection
would have worked as well. So let's solve a problem that would be hard to
solve without bisection (or some other numeric approximation algorithm).
Specifically, find a number x such that x**5 == 2**x:
print("use bisection to approximate x where x**5 == 2**x")
deff3(x):return x**5 - 2**x # f(1)<0, f(2)>0
x = findZeroWithBisection(f3, 1, 2, 0.000000001)
print(f" x = {x}") # prints x = 1.17727855081
print(f" check: x**5 - 2**x = {x**5 - 2**x}") # prints check: x**5 - 2**x = 3.63570817896e-09 (great!)
Hopefully this bisection excursion helps you appreciate the awesome
computational power that about 10 lines of Python can have!
carrylessAdd(x, y) [15 pts]
First, you may wish to read the first page (page 44) from
here about Carryless Arithmetic.
Or, just understand that carryless addition is what it sounds like --
regular addition, only with the carry from each column ignored.
So, for example, if we carryless-ly add 8+7, we get 5 (ignore the carry).
And if we add 18+27, we get 35 (still ignore the carry).
With this in mind, write the function carrylessAdd(x, y) that takes two non-negative integers x and y and returns their carryless sum. As the paper demonstrates, carrylessAdd(785, 376) returns 51.
longestDigitRun(n) [15 pts]
Write the function longestDigitRun(n) that takes a possibly-negative int value n and returns the digit that has the longest consecutive run, or the smallest such digit if there is a tie. So, longestDigitRun(117773732) returns 7 (because there is a run of 3 consecutive 7's), as does longestDigitRun(-677886).
playPig() [20 pts] [manually graded]
Notes:
For only this problem, you may use strings but only in these ways:
Obtain a string from input().
Check if two strings are equal.
Print a string (including f-strings).
With that, first, read about the dice game Pig
here.
Then, write the function playPig(), that allows two players to play
the game Pig. You will want to use random.randint(1,6) to randomly
choose a number between 1 and 6 inclusive. Grading criteria:
Your game must enforce the basic rules of Pig.
Your game should not use any graphics. This is a console-based game.
Your game should use the input(prompt) function to get user input.
Your game should print enough information to make the game reasonably fun and usable.
Be thoughtful but don't overthink this.
Any reasonably fun, playable game of Pig will do fine here. Have fun!
Bonus/Optional: bonusCarrylessMultiply(x, y) [1 pts]
Write the function bonusCarrylessMultiply(x, y), that works similarly to carrylessAdd(x, y) (which we will cover in the writing session), based on
this paper on Carryless Arithmetic. This paper shows that bonusCarrylessMultiply(643, 59) returns 417. Hint: it may help if you do this differently than usual long multiplication. Instead of working by rows in the output, work by columns. So first compute all the ones digit values, and sum those mod 10. Then compute all the tens digit values, and sum those mod 10. And so on. You may assume x and y are non-negative.
Hint #1: do not solve carrylessMultiply(x, y) by simply calling carrylessAdd(x, result) a total of y times. That is wrong on two levels. First, it is simply too inefficient (what if we are multiplying 20-digit numbers?). Second, it is also wrong algorithmically! CarrylessMultiply is not like normal multiply, and if we take + to be carrylessAdd and * to be carrylessMultiply, then it is not necessarily true that (x * y) is the same as (x + x + ... + x + x) for a total of y x's. Yikes. So: stick with the next hint (see below). It also uses carrylessAdd and is fairly straightforward, but it is reasonable efficient and algorithmically correct. Good luck with it.
Hint #2: Here's a hint on one way to solve this problem (there are many ways, and this way is not the most efficient, to be sure, but it is efficient enough and it is perhaps among the clearest and easiest ways).
in this way, we actually only have to multiply 123 times a single digit each time, then multiply that result by the right power of 10. Right?
Ok, so now, to multiply by a single digit, we can instead just add that many times. That is:
123 * 6 == 123 + 123 + 123 + 123 + 123 + 123
Why is that interesting? Because we already have carrylessAdd, so we can just use that to do all this addition.
Of course, multiplying by simply adding is very inefficient. But since we are only doing it for multiplying by a single digit, there's a max of 9 additions (8 if you think about it), and so it's not so inefficient. It's actually acceptable, if not ideal, and certainly good enough for hw2,
though again better approaches do exist.
Hope this helps. And, again, you can safely ignore all of this and solve this problem any way you wish.
Bonus/Optional: Integer Data Structures [2 pts] Note: We will not provide debugging help or hints on this problem! You must rely on your own
problem-solving and debugging skills. That being said, we have offered rather detailed guidance here. You should only do this if you find it interesting and fun, because it's really not worth
spending many hours on this purely for 2 points. That being said, we hope you enjoy it!