CMU 15-112: Fundamentals of Programming and Computer Science
Homework 1 (Due Sat 4-Sep at 8pm ET)
(Optional Early Bird Bonus for Part A is Due Thu 2-Sep at 10pm ET)


Important Notes:
  1. Do not start this homework until you have first carefully read the 112 syllabus and submitted the 112 Student Contract!
  2. 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.
  3. This homework includes an Early Bird Bonus. If you submit Part A to the "early-bird" category in Autolab by the early-bird deadline (Thu at 10pm), and if you receive full credit on that submission, then:
    1. You will receive +1 points of bonus on this homework, and
    2. You are excused from Friday's lab (where we will review hints and possibly even full solutions for Part A). Of course, you may still attend if you prefer.
  4. The "early-bird" submission is only for the Early Bird Bonus. In particular, you still must submit all of Part A as well as Part B in Autolab (in the "hw" category) before the hw submission deadline (Sat at 8pm). Your hw grade is only based on that submission.
  5. To start:
    1. Create a folder named 'week1'
    2. Download both hw1.py and cs112_f21_week1_linter.py to that folder
    3. Edit hw1.py using VSCode
    4. When you have completed and fully tested hw1, submit hw1.py to Autolab. For this hw, you may submit up to 10 times to the early-bird submission and up to 10 more times for the main hw submission (which is more than you should require), but only your last submission counts.
  6. Do not use string indexing, loops, lists, list indexing, or recursion this week.
  7. Do not hardcode the test cases in your solutions.
  8. Hint: The starter hw1.py file includes test functions. You may comment out individual test functions in testAll() to temporarily bypass those tests. The autograder will run all the tests in any case. Ask a TA if you need help with this.
  9. Finally, several problems below include video sample solutions. This is just to get you started this week, and will not be a general feature of hw's. Feel free to watch the videos, but still, then solve the problem on your own (though then, sure, your code will look much like the code from the video -- that's fine for those problems).

Part A [20 pts]

  1. distance(x1, y1, x2, y2) [2 pts]
    Write the function distance(x1, y1, x2, y2) that takes four int or float values x1, y1, x2, y2 that represent the two points (x1, y1) and (x2, y2), and returns the distance between those points as a float.

  2. circlesIntersect(x1, y1, r1, x2, y2, r2) [2 pts]
    Write the function circlesIntersect(x1, y1, r1, x2, y2, r2) that takes 6 numbers (ints or floats) -- x1, y1, r1, x2, y2, r2 -- that describe the circle centered at (x1,y1) with radius r1, and the circle centered at (x2,y2) with radius r2, and returns True if the two circles intersect and False otherwise.

  3. getInRange(x, bound1, bound2) [2 pts]
    Write the function getInRange(x, bound1, bound2) which takes 3 int or float values -- x, bound1, and bound2, where bound1 is not necessarily less than bound2. If x is between the two bounds, just return it unmodified. Otherwise, if x is less than the lower bound, return the lower bound, or if x is greater than the upper bound, return the upper bound. For example:
    • getInRange(1, 3, 5) returns 3 (the lower bound, since 1 lies to the left of the range [3,5])
    • getInRange(4, 3, 5) returns 4 (the original value, since 4 is in the range [3,5])
    • getInRange(6, 3, 5) returns 5 (the upper bound, since 6 lies to the right of the range [3,5])
    • getInRange(6, 5, 3) also returns 5 (the upper bound, since 6 lies to the right of the range [3,5])

  4. eggCartons(eggs) [2 pts]
    Write the function eggCartons(eggs) that takes a non-negative integer number of eggs, and returns the smallest integer number of cartons required to hold that many eggs, where a carton may hold up to 12 eggs.

  5. pascalsTriangleValue(row, col) [2 pts]
    Write the function pascalsTriangleValue(row, col) that takes int values row and col, and returns the value in the given row and column of Pascal's Triangle where the triangle starts at row 0, and each row starts at column 0. If either row or col are not legal values, return None, instead of crashing. Hint: math.factorial may be helpful! Also: it may help to read MathIsFun's Pascal's Triangle page, which includes a general discussion, some nice applications, and further down the page a helpful formula.

  6. getKthDigit(n, k) [5 pts]
    Write the function getKthDigit(n, k) that takes a possibly-negative int n and a non-negative int k, and returns the kth digit of n, starting from 0, counting from the right. So:

    getKthDigit(789, 0) == 9 getKthDigit(789, 1) == 8 getKthDigit(789, 2) == 7 getKthDigit(789, 3) == 0 getKthDigit(-789, 0) == 9

  7. setKthDigit(n, k, d) [5 pts]
    Write the function setKthDigit(n, k, d) that takes three integers -- n, k, and d -- where n is a possibly-negative int, k is a non-negative int, and d is a non-negative single digit (between 0 and 9 inclusive). This function returns the number n with the kth digit replaced with d. Counting starts at 0 and goes right-to-left, so the 0th digit is the rightmost digit. For example:

    setKthDigit(468, 0, 1) == 461 setKthDigit(468, 1, 1) == 418 setKthDigit(468, 2, 1) == 168 setKthDigit(468, 3, 1) == 1468

Part B [80 pts]

  1. Friday Lab [10 pts]
    Attend your Friday lab (in your assigned recitation time). To receive full credit, you must show up on time, stay the whole time, be focused and uni-tasking (not multi-tasking), and fully participating. Note that if you received the Early Bird Bonus, you are excused from attending Friday lab, though you are still most welcome to attend.

  2. nearestOdd(n) [10 pts]
    Write the function nearestOdd(n) that takes an int or float n, and returns as an int value the nearest odd number to n. In the case of a tie, return the smaller odd value. Note that the result must be an int, so nearestOdd(13.0) is the int 13, and not the float 13.0.

    Hint: Remember that the built-in round function works in surprising ways. Instead of round(n), you should use roundHalfUp(n) from this week's notes. That said, there are good ways to solve this problem without using rounding at all, if you prefer.

  3. numberOfPoolBalls(rows) [10 pts]
    Pool balls are arranged in rows where the first row contains 1 pool ball and each row contains 1 more pool ball than the previous row. Thus, for example, 3 rows contain 6 total pool balls (1+2+3). With this in mind, write the function numberOfPoolBalls(rows) that takes a non-negative int value, the number of rows, and returns another int value, the number of pool balls in that number of full rows. For example, numberOfPoolBalls(3) returns 6. We will not limit our analysis to a "rack" of 15 balls. Rather, our pool table can contain an unlimited number of rows. For this problem and the next, you should research Triangular Numbers.

  4. numberOfPoolBallRows(balls) [25 pts]
    This problem is the inverse of the previous problem. Write the function numberOfPoolBallRows(balls) that takes a non-negative int number of pool balls, and returns the smallest int number of rows required for the given number of pool balls. Thus, numberOfPoolBallRows(6) returns 3. Note that if any balls must be in a row, then you count that row, and so numberOfPoolBallRows(7) returns 4 (since the 4th row must have a single ball in it).

  5. colorBlender(rgb1, rgb2, midpoints, n) [25 pts]
    This problem implements a color blender, inspired by this tool. In particular, we will use it with integer RGB values (it also does hex values and RGB% values, but we will not use those modes). Note that RGB values contain 3 integers, each between 0 and 255, representing the amount of red, green, and blue respectively in the given color, where 255 is "entirely on" and 0 is "entirely off".

    For example, consider this case. Here, we are combining crimson (rgb(220, 20, 60)) and mint (rgb(189, 252, 201)), using 3 midpoints, to produce this palette (using our own numbering convention for the colors, starting from 0, as the tool does not number them):

    color0: rgb(220, 20, 60) color1: rgb(212, 78, 95) color2: rgb(205, 136, 131) color3: rgb(197, 194, 166) color4: rgb(189, 252, 201)

    There are 5 colors in the palette because the first color is crimson, the last color is mint, and the middle 3 colors are equally spaced between them.

    So we could ask: if we start with crimson and go to mint, with 3 midpoints, what is color #1? The answer then would be rgb(212, 78, 95).

    One last step: we need to represent these RGB values as a single integer. To do that, we'll use the first 3 digits for red, the next 3 for green, the last 3 for blue, all in base 10 (decimal, as you are accustomed to). Hence, we'll represent crimson as the integer 220020060, and mint as the integer 189252201.

    With all that in mind, write the function colorBlender(rgb1, rgb2, midpoints, n), which takes two integers representing colors encoded as just described, a non-negative integer number of midpoints, and a non-negative integer n, and returns the nth color in the palette that the tool creates between those two colors with that many midpoints. If n is out of range (too small or too large), return None.

    For example, following the case above: colorBlender(220020060, 189252201, 3, 1) returns 212078095

    Hints: RGB values must be ints, not floats. Also, remember to use roundHalfUp(n) instead of round(n) when calculating midpoint colors.

Note: we usually offer optional bonus problems for students who want an extra challenge. These problems are optional, and should only be attempted after the rest of the assignment has been finished. If you attempt a bonus problem, you should uncomment the corresponding test function call at the bottom of the file in testAll() to test your solution!
  1. Bonus/Optional: bonusPlayThreeDiceYahtzee(dice) [2.5 pts]
    In this exercise, we will write a simplified form of the dice game Yahtzee. In this version, the goal is to get 3 matching dice, and if you can't do that, then you hope to at least get 2 matching dice. The game is played like so:
    1. Roll 3 dice.
    2. If you do not have 3 matching dice:
      1. If you have 2 matching dice (a pair), keep the pair and roll one die to replace the third die.
      2. Otherwise, if you have no matching dice, keep the highest die and roll two dice to replace the other two dice.
    3. Repeat step 2 one more time.
    4. Finally, compute your score like so:
      1. If you have 3 matching dice, your score is 20 + the sum of the matching dice.
        • So if you have 4-4-4, your score is 20+4+4+4, or 32.
      2. If you only have 2 matching dice, your score is 10 + the sum of the matching dice.
        • So if you have 4-4-3, your score is 10+4+4, or 18.
      3. If you have no matching dice, your score is the highest die.
        • So if you have 4-3-2, your score is just 4.
    Our goal is to write some Python code that plays this game. It's a large task, so we will use top-down design and break it up into smaller, more manageable pieces. So we'll first write some helper functions that do part of the work, and then those helper functions will make our final function much easier to write.

    Also note: we will represent a hand of 3 dice as a single 3-digit integer. So the hand 4-3-2 will be represented by the integer 432. With that, let's start writing some code. Be sure to write your functions in the same order as given here, since later functions will make use of earlier ones!

    1. handToDice(hand)
      Write the (very short) function handToDice(hand) that takes a hand, which is a 3-digit integer, and returns 3 values, each of the 3 dice in the hand. For example:
      assert(handToDice(123) == (1,2,3))
      assert(handToDice(214) == (2,1,4))
      assert(handToDice(422) == (4,2,2))
      Hint: You might find // and % useful here, and also getKthDigit.

    2. diceToOrderedHand(a, b, c)
      Write the function diceToOrderedHand(a, b, c) that takes 3 dice and returns them in a hand, which is a 3-digit integer. However, even if the dice are unordered, the resulting hand must be ordered so that the largest die is on the left and smallest die is on the right. For example:
      assert(diceToOrderedHand(1,2,3) == 321)
      assert(diceToOrderedHand(6,5,4) == 654)
      assert(diceToOrderedHand(1,4,2) == 421)
      assert(diceToOrderedHand(6,5,6) == 665)
      assert(diceToOrderedHand(2,2,2) == 222)
      Hint: You can use max(a,b,c) to find the largest of 3 values, and min(a,b,c) to find the smallest.

    3. playStep2(hand, dice)
      This is the most complicated part. Write the function playStep2(hand, dice) that plays step 2 as explained above. This function takes a hand, which is a 3-digit integer, and it also takes dice, which is an integer containing all the future rolls of the dice. For example, if dice is 5341, then the next roll will be a 1, then the roll after that will be a 4, then a 3, and finally a 5. Note that in a more realistic version of this game, instead of hard-coding the dice in this way, we'd probably use a random-number generator.

      With that, the function plays step2 of the given hand, using the given dice to get the next rolls as needed. At the end, the function returns the new hand, but it has to be ordered, and the function also returns the resulting dice (which no longer contain the rolls that were just used).

      For example:
      assert(playStep2(413, 2312) == (421, 23))
      Here, the hand is 413, and the future dice rolls are 2312. What happens? Well, there are no matching dice in 413, so we keep the highest die, which is a 4, and we replace the 1 and the 3 with new rolls. Since new rolls come from the right (the one's digit), those are 2 and 1. So the new hand is 421. It has to be sorted, but it already is. Finally, the dice was 2312, but we used 2 digits, so now it's just 23. We return the hand and the dice, so we return (421, 23).

      Here are some more examples. Be sure you carefully understand them:
      assert(playStep2(413, 2345) == (544, 23))
      assert(playStep2(544, 23) == (443, 2))
      assert(playStep2(544, 456) == (644, 45))
      Hint: You may wish to use handToDice(hand) at the start to convert the hand into the 3 individual dice.
      Hint: Then, you may wish to use diceToOrderedHand(a, b, c) at the end to convert the 3 dice back into a sorted hand.
      Hint: Also, remember to use % to get the one's digit, and use //= to get rid of the one's digit.

    4. score(hand)
      Almost there... Now write the function score(hand) that takes a 3-digit integer hand, and returns the score for that hand as explained in step4 above. For example:
      assert(score(432) == 4)
      assert(score(532) == 5)
      assert(score(443) == 10+4+4)
      assert(score(633) == 10+3+3)
      assert(score(333) == 20+3+3+3)
      assert(score(555) == 20+5+5+5)
      Hint: The structure of this function is actually quite similar to the previous function.

    5. bonusPlayThreeDiceYahtzee(dice)
      Ok, we've made it to the last function: bonusPlayThreeDiceYahtzee(dice), the function that actually earns the 2.5 bonus points! This function takes one value, the dice with all the rolls for a game of 3-Dice Yahtzee. The function plays the game -- it does step1 and gets the first 3 dice (from the right), then it does step2 twice (by calling playStep2, which you already wrote), and then it computes the score (by calling score, which you already wrote). The function should return two values -- the resulting hand and the score for that hand. For example:
      assert(bonusPlayThreeDiceYahtzee(2312413) == (432, 4))
      assert(bonusPlayThreeDiceYahtzee(2315413) == (532, 5))
      assert(bonusPlayThreeDiceYahtzee(2345413) == (443, 18))
      assert(bonusPlayThreeDiceYahtzee(2633413) == (633, 16))
      assert(bonusPlayThreeDiceYahtzee(2333413) == (333, 29))
      assert(bonusPlayThreeDiceYahtzee(2333555) == (555, 35))

  2. Bonus/Optional: bonusFindIntRootsOfCubic(a,b,c,d) [2.5 pts]
    Write the function bonusFindIntRootsOfCubic(a,b,c,d) that takes the int or float coefficients a, b, c, d of a cubic equation of this form:

         y = ax3 + bx2 + cx + d

    You are guaranteed the function has 3 real roots, and in fact that the roots are all integers. Your function should return these 3 roots in increasing order. How does a function return multiple values? Like so:

    return root1, root2, root3
    To get started, you'll want to read about Cardano's cubic formula here. Then, from that page, use this formula:
     
    x   =   {q + [q2 + (r-p2)3]1/2}1/3   +   {q - [q2 + (r-p2)3]1/2}1/3   +   p

    where:

    p = -b/(3a),   q = p3 + (bc-3ad)/(6a2),   r = c/(3a)

    This isn't quite as simple as it seems, because your solution for x will not only be approximate (and not exactly an int, so you'll have to do something about that), but it may not even be real! Though the solution is real, the intermediate steps may include some complex values, and in these cases the solution will include a (possibly-negligibly-small) imaginary value. So you'll have to convert from complex to real (try c.real if c is complex), and then convert from real to int.

    Great, now you have one root. What about the others? Well, we can divide the one root out and that will leave us with a quadratic equation, which of course is easily solved. A brief, clear explanation of this step is provided here. Don't forget to convert these to int values, too!

    So now you have all three int roots. Great job! All that's left is to sort them. Now, if this were later in the course, you could put them in a list and call a built-in function that will sort for you.; But it's not, so you can't. Instead, figure out how to sort these values using the limited built-in functions and arithmetic available this week. Then just return these 3 values and you're done.


carpe diem