Featured Post

The Pin of Contents

OI! CLICK DIS TO HELP YA FIND YER WAY! Your hub for everything Gordo... if you happen to share my narrow view of what 'everything Gor...

Friday, February 12, 2016

L2Code! MIT 6.00 Problem Set 1 pt. 2

Now that we're the gods of... um, very limited credit card debt calculation... Okay, let's dial it back.

Today we're gonna refine the debt calculator. Problem 2 and 3 are "Paying Off Debt In a Year" and "Using Bisection Search to Make the Program Faster."

I'm gonna approach this believing that 2 and 3 are easier than 1 because I'm a masochist.


Goin' off da rails
All aboard the crazy train!

Okeydoke. Here're the instructions for Problem 2:

Paying Debt Off In a Year

*******Problem 2

Now write a program that calculates the minimum fixed monthly payment needed in order pay off a credit card balance within 12 months. We will not be dealing with a minimum monthly payment rate.

Take as raw_input() the following floating point numbers:

1. the outstanding balance on the credit card

2. annual interest rate as a decimal Print out the fixed minimum monthly payment, number of months (at most 12 and possibly less than 12) it takes to pay off the debt, and the balance (likely to be a negative number).

Assume that the interest is compounded monthly according to the balance at the start of the month (before the payment for that month is made). The monthly payment must be a multiple of $10 and is the same for all months. Notice that it is possible for the balance to become negative using this payment scheme. In short:

Monthly interest rate = Annual interest rate / 12.0
Updated balance each month = Previous balance * (1 + Monthly interest rate) – Minimum monthly payment.******

This time, I'll actually post the Jeopardy! portion. If anybody besides me ever gives a damn, maybe I'll update the other post with that, too.

 ******Test Case 1

>>> Enter the outstanding balance on your credit card: 1200
Enter the annual credit card interest rate as a decimal: .18
RESULT
Monthly payment to pay off debt in 1 year: 120
Number of months needed: 11
Balance: -10.05 >>>

Test Case 2
>>> Enter the outstanding balance on your credit card: 32000
Enter the annual credit card interest rate as a decimal: .2
RESULT
Monthly payment to pay off debt in 1 year: 2970
Number of months needed: 12
Balance: -74.98 >>>

Hints Start at $10 payments per month and calculate whether the balance will be paid off (taking into account the interest accrued each month). If $10 monthly payments are insufficient to pay off the debt within a year, increase the monthly payment by $10 and repeat.******

I'm confused about one thing: why's the first one only require 11 months to pay off? The whole point is to balance it over a year! If we don't, why not just pay off all the balance at once? I mean, he didn't give me any budget limitations =P

Alright, I'll quit judging. We'll get started! He's requested we use a brute force approach (or, rather, 'hinted' that we should) so that's what we'll do. Some similarities to problem 1: while there is no 'minimum payment,' there is a static payment each month; a number that increases after each 12-month cycle. Ah, it occurs to me: we need a loop within a loop for this, because we have a 12-month cycle we need to repeat an undefined amount of times until the optimal rate is calculated.

Anyway, similarities: statpay will be sorta like minpay in the last problem. We need the yearlytotal variable again, in a nearly identical role. The change there is that it will serve the additional purpose of telling the undefined 'meta'loop whether or not the yearly cycle needs to repeat with a bigger statpay. outsbal, oribal, annint, and deadline all make comebacks, too. I kinda feel ready to start with that. So: first off. Tell me what ya done, indebted.

outsbal = float(raw_input('Enter your debt, dummy: '))
annint = float(raw_input('Enter their skim rate in decimal form, dummy: '))
oribal = outsbal
statpay = float(0)

Easy peasy. What's a peasy? Stupid brit commercials. Anyhooooo. Are we equipped to set up the metaloop or the innerloop first? Inner's prolly easier, yes? It's a lot like the last one. Ok then:

deadline = 12
yearlytotal = float(0)
statpay +=10
outsbal = oribal
while deadline > 0 and outsbal >= 0.00:
    principle = round(statpay-((annint*outsbal)/12),2)
    outsbal -= principle
    yearlytotal += statpay
    deadline -= 1

First glance: outside this loop, we set the deadline to 12. reset the yearlytotal to 0, increase statpay by 10, and reset the outsbal back to the original debt. These are all things that need to happen in the metaloop but can't happen in the yearly loop.

In the loop, we have principle calculating, and we're also calculating how much the statpay will chip off the outsbal each month. yearlytotal is keeping track of how much gets paid in total... come to think of it, the assignment doesn't strictly state we need to track it. Ah well, won't hurt. Finally, deadline is subtracting 1 from itself each time to make sure we only repeat this cycle 12 times.

As written, this loop will stay stuck on $10 for eternity, so next we need to determine how to fit all this into the metaloop. So: what's the criteria for the metaloop? Well, as long as outsbal isn't reduced to 0 or less in the inner loop, then we need to keep repeating. Are there any other criteria? I don't think so... let's give it a look.

while outsbal > 0.00:

Wait... we really just need to add that before deadline? Wow, how did I not foresee that? Whatever, don't dwell. Okay. To test what we have, we need some print. So: what do we need to know when it terminates? Mostly just statpay and the negative balance. Okeydoke then:

print 'With a monthly payment of $', statpay, ', your remaining balance will be $,' outsbal, 'at the end of ' 12-deadline, 'months.'

I think that does it. This is all supposing that the variables in the inner loop don't get reset somehow. But why would they? Hm. If this breaks, somebody put that quote on my grave. (Looks like I get a better epitaph! Maybe)

Success! We did end up having to refine it a bit (forgot some commas, a colon, and we were being silly with deadline at 11 instead of 12) So here's what the final code looks like:

outsbal = float(raw_input('Enter your debt, dummy: '))
annint = float(raw_input('Enter their skim rate, dummy: '))
statpay = float(0)
oribal = outsbal

while outsbal > 0.00:
    deadline = 12
    yearlytotal = float(0)
    statpay +=10
    outsbal = oribal
    while deadline > 0 and outsbal >= 0.00:
        principle = round(statpay-((annint*outsbal)/12),2)
        outsbal -= principle
        yearlytotal += statpay
        deadline -= 1
print 'With a monthly payment of $', statpay, ', your remaining balance will be $,', outsbal, 'at the end of ', 12-deadline, 'months.'

This completes the problem... for the most part. I wanna add a feature or 2. First, we were tracking our yearlytotal, may as well print it. Then we want it to tell us how many times the metaloop ran. SO! We add a variable called 'cycles' and then this to the metaloop:

cycles += 1

Then we have the printscript go as follows:

print 'With a monthly payment of $', statpay, ', your remaining balance will be $,', outsbal, 'at the end of ', 12-deadline, 'months. Why did you pay them a total of $', yearlytotal, ', dummy?!'

print 'By the by, it took me ', cycles, 'to figure this out.'

Hm. Do we wanna correct it so it always calculates for a year? Nah, it's probably just a matter of decreasing the increment of statpay. Speaking of which, time for problem 3~!

******Using Bisection Search to Make the Program Faster

You’ll notice that in problem 2, your monthly payment had to be a multiple of $10. Why did we make it that way? In a separate file, you can try changing the code so that the payment can be any dollar and cent amount (in other words, the monthly payment is a multiple of $0.01). Does your code still work? It should, but you may notice that your code runs more slowly, especially in cases with very large balances and interest rates.

How can we make this program faster? We can use bisection search (to be covered in lecture 3)!

We are searching for the smallest monthly payment such that we can pay off the debt within a year. What is a reasonable lower bound for this value? We can say $0, but you can do better than that. If there was no interest, the debt can be paid off by monthly payments of one-twelfth of the original balance, so we must pay at least this much. One-twelfth of the original balance is a good lower bound.

What is a good upper bound? Imagine that instead of paying monthly, we paid off the entire balance at the end of the year. What we ultimately pay must be greater than what we would’ve paid in monthly installments, because the interest was compounded on the balance we didn’t pay off each month. So a good upper bound would be one-twelfth of the balance, after having its interest compounded monthly for an entire year.

In short:

Monthly payment lower bound = Balance / 12.0
Monthly payment upper bound = (Balance * (1 + (Annual interest rate / 12.0)) ** 12.0) / 12.0

Problem 3

Write a program that uses these bounds and bisection search (for more info check out the Wikipedia page here) to find the smallest monthly payment to the cent (no more multiples of $10) such that we can pay off the debt within a year. Try it out with large inputs, and notice how fast it is. Produce the output in the same format as you did in problem 2. ******

Then, Jeopardy!

******Test Case 1

 >>> Enter the outstanding balance on your credit card: 320000
Enter the annual credit card interest rate as a decimal: .2
RESULT
Monthly payment to pay off debt in 1 year: 29643.05
Number of months needed: 12
Balance: -0.1

>>> Test Case 2 >>> Enter the outstanding balance on your credit card: 999999
Enter the annual credit card interest rate as a decimal: .18
RESULT
 Monthly payment to pay off debt in 1 year: 91679.91
Number of months needed: 12
Balance: -0.12 >>> ******

Now I look like an idiot for whining about 11 months earlier, because this totally fixes that. Okay! Let's code it out. All we really gotta do is add the bisection equation to this. So! ...um...

Variables: upbound, lowbound, calculated off the raw input as such:

lowbound = oribal/12.0
upbound = (oribal * (1 + (annint/12.0))**12.0)/12.0

Then the bisection function is easy: c = (a+b)/2. But since the solution to this equation might need to become the new upbound or lowbound depending on the answer. This one's makin' me head a little fuzzy! Bit harder than problem 2 just because of the math. Anyway, power through:

We need to make the metaloop more precise. outsbal >0 no longer works because the upper bound will force the result to go super negative and fail us in the task of finding the minimum amount to pay it off in a year. This means the balance needs to be negative, but not TOO negative; since we're determining to the nearest penny, that means the balance shouldn't be smaller than -11 cents or bigger than 0, no?

on that line of logic: if the balance is negative, our upper bound was too high. If the balance is positive, the lower bound was too low. Therefore: if outsbal >0 at the end of the loop, lowbound = statpay.

That can be accomplished with an 'if, else' clause. It's down there in the metaloop and does exactly what the paragraph above describes. What's important about its placement: this needs to happen in the metaloop, can't interfere with the initial definition of statpay, and must alter low or upbound before outsbal or statpay change.

outsbal = float(raw_input('Enter your debt, dummy: '))
annint = float(raw_input('Enter their skim rate, dummy: '))
oribal = outsbal
lowbound = round(oribal/12.0,2)
upbound = round((oribal * (1 + (annint/12.0))**12.0)/12.0,2)
statpay = lowbound
cycles = 0

while outsbal > 0 or outsbal < -0.11:
    yearlytotal = float(0)
    if outsbal > 0:
            lowbound = statpay
    else:
            upbound = statpay
    statpay = round((lowbound + upbound)/2,2)
    outsbal = oribal
    cycles += 1
    for month in range (1,13):
        principle = round(statpay-((annint*outsbal)/12),2)
        outsbal -= round(principle,2)
        yearlytotal += round(statpay,2)
             
print 'With a monthly payment of $', statpay, ', your remaining balance will be $,', round(outsbal,2), 'at the end of ', month, 'months. Why did you pay them a total of $', round(yearlytotal,2), ', dummy?!'

print 'By the by, it took me ', cycles, ' cycles to figure this out.'

This code almost works - it works for the first value, 320000, but not 999999. It enters an infinite loop on the 2nd value. Looking at Jeopardy!, the balance is exactly -0.12. Could it be that we can't have -.11 as our limit in the metaloop?! Let's try it: failure, still loops. We're check out some recitations...

One thing we learned is that a 'for' loop with a range of 1,13 might be more appropriate for our inner loop. The reason the 13 is there is because it doesn't actually calculate with the upper bound of the range. Also, our problem seems to come from the upbound portion of our if-else loop, particularly the 'else' portion. What happens if we throw an 'elif' for the 'success' range? Nothing, still breaks.

Perhaps the program doesn't know how to settle for 'good enough.' The solution includes a clause that breaks the metaloop when the upper bound and lower bound are within .005 of each other. What if we change our while loop to an if loop, like that one?

This is where we land:

#The following initial variables seem to be good:
oribal = float(raw_input('Enter your debt, dummy: '))
annint = float(raw_input('Enter their skim rate, dummy: '))
#These variables also seem to be good
outsbal = oribal
lowbound = outsbal/12
upbound = (outsbal*(1+(annint/12))**12)/12
cycles = 0
#The solution does things our code doesn't: first, it bothers to calculate the interest
#each month and adds it to the balance. Is this wrong? It seems like it would be
#good enough to simply calculate what amount of principle gets paid each month.
#It returned a good result, but could it contribute to the infinite loop? Perhaps.
#Let's modify our code to do what theirs does.
#This is where our program diverges from the solution. They start a boolean here.
while True:
   outsbal = oribal
   statpay = (lowbound + upbound)/2
   cycles+=1
   ##This portion simulates the passage of time. It'll run 12 times or until
   ##outsbal hits 0 or lower.
   for month in range(1,13):
      interest = round(outsbal*annint/12,2)
      outsbal += interest - statpay
      if outsbal <= 0:
         break
   ##Once the above loop is broken, this segment has 3 branches depending
   ##on the outcome. Once the difference between the upper and lower bound is
   ##less than half a penny, the program has determined the monthly payment to
   ##the nearest penny, rounds to it, and calculates the minimum monthly payment
   ##to pay it off in one year. If the balance dips below 0 but the difference
   ##between upper and lower bound is not less than half a penny, the program
   ##believes you have paid too much and begins anew with that monthly payment
   ## as the new upper limit. If neither of these conditions is met it's because
   ## you failed to pay off the loan with that monthly payment, so the program
   ## begins to promise anew with that payment as the new lower limit. I believe
   ## my version of the program, commented out below, could fail because it could
   ## infinitely find upper and lower bounds that never satisfied the criteria to
   ## break the while loop.
   if (upbound - lowbound < 0.005):
      statpay = round(statpay+0.004999,2)
      outsbal = oribal
      for month in range(1,13):
         interest = round(outsbal*annint/12,2)
         outsbal += interest - statpay
         if outsbal <= 0:
            break
      print 'With a monthly payment of $', statpay, ', your remaining balance will be $,', +round(outsbal,2), 'at the end of ', month, 'months. Why did you pay them a total of $', ##+round(yearlytotal,2), ', dummy?!'
      print 'By the by, it took me ', cycles, ' cycles to figure this out.',
      break
   elif outsbal <0:
      upbound = statpay
   else:
      lowbound = statpay
   

#rough time wrapping my head around this order of operations, but let's see.
#The while True: conditionals are outsbal = oribal and statpay = lowbound+ubpound/2.
#When one of those becomes false, then it terminates the loop? Of note here is
#the fact that statpay is altered


##Failed Code
##while outsbal > 0 or outsbal < -0.11:
##    yearlytotal = float(0)
##    if outsbal > 0:
##            lowbound = statpay
##    else:
##            upbound = statpay
##    statpay = round((lowbound + upbound)/2.0,2)
##    outsbal = oribal
##    cycles += 1
##    for month in range (1,13):
##        principle = round(statpay-((annint*outsbal)/12.0),2)
##        outsbal -= round(principle,2)
##        yearlytotal += round(statpay,2)
##              
##print 'With a monthly payment of $', statpay, ', your remaining balance will be $,', +round(outsbal,2), 'at the end of ', month, 'months. Why did you pay them a total of $', +round(yearlytotal,2), ', dummy?!'
##
##print 'By the by, it took me ', cycles, ' cycles to figure this out.'

We're calling this an almost-success and signing off because we're tiiiiiiiiiired.

Last stop, everybody off!

No comments:

Post a Comment