简体   繁体   中英

Bisect Search to choose best savings rate

Hi I would like some help with this questions set as one of the problems on the MIT OCW Computer Science and Python Course. I know people have asked similar questions and I have found useful posts such as Bisection search code doesnt work but I am still stuck!

I have struggled with this problem for many days and tried to tackle it in different ways and failed in all ways. If at all possible, could somebody just hint at where I am going wrong, rather than telling me the answer. I would like to solve this problem for myself with bit of help.

For reference, the question is part C, here: https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-0001-introduction-to-computer-science-and-programming-in-python-fall-2016/assignments/MIT6_0001F16_ps1.pdf

As I have been struggling, I have broken down this task into an overall aim and then into steps to solve the problem.

Aim: try to find the best rate of savings to achieve a down payment on a $1M house in 36 months ##Steps to solve the problem:
1) make a guess on the savings rate, which is the average of the 0 and 1000
2) calculate how that would grow over 36 months
3a) if amount reached goes over 25% of $1m within 36 months, then a lower savings rate should be the new guess
...max=guess (old guess) and min=0 and update the guess (average of high and low)
...run the calculation in step 2 with the new guess
3b) if amount does not reach 25% of $1m within 36 months, then a higher savings rate should be the new guess
...min=guess (old guess) and update the guess (average of high and low) ...run the calculation in step 2 with the new guess
3c) if amount reaches 25% of $1m within the 36th month deadline then quit and record the savings rate as the best guess.
For simplicity: assume no interest and assume wages remain same

So here's my code with the current effort at solving this. (it leads to the "guess" variable tending to 0 and then infinitely looping)

total_cost=1000000 #cost of house
portion_down_payment=0.25 #fraction of cost needed for downpayment on house
downpayment=total_cost*portion_down_payment

starting_annual_salary=float(input("Enter the starting salary: "))
low=0
high=1000
bisect_steps=0
month=1 #set as 1 because the first calculation will occur in month 1 
guess=(low+high)//2
current_savings=0

def calSavings(current_savings,monthly_salary,guess,month):
    while month<37:
        monthly_savings=monthly_salary*(guess/1000)
        current_savings+=monthly_savings
        month+=1
    return(current_savings) 

current_savings=calSavings(current_savings,monthly_salary,guess,1)
while True:
    current_savings=calSavings(current_savings,monthly_salary,guess,1)
    if current_savings>downpayment and month<=35: #if amount reached goes over 25% of $1m within 36 months
        #a lower savings rate should be the new guess
        high=guess #max=guess (old guess) and min=0 and update the guess
        bisect_steps+=1
        guess=(low+high)//2
        print("The guess was too high, so the new lower guess is",guess," This is bisect step",bisect_steps)
        continue #send new guess up to beginning of while loop to calculate 
    elif current_savings<downpayment and month>=36: #if amount does not reach 25% of $1m within 36 months
        low=guess
        bisect_steps=+1
        guess=(low+high)//2
        print("The guess was too low, so the new higher guess is",guess," This is bisect step",bisect_steps)
        continue #send new guess up to beginning of while loop to calculate 
    elif current_savings>=downpayment and month==36: #if amount reaches 25% of $1m in the 36th months then quit
        # record the savings rate as the best guess
        print("The best savings rate is ",guess/100,"%, the amount saved was ",current_savings," in ",month," months")
        break #break out of while loop

I know other people have asked similar questions (I have looked at those answers and still not solved my problem) but more than an answer I want help on HOW to solve this problem.

Update

The reason why your loop isn't stopping is because you aren't giving it enough time. What you are forgetting is that you're dealing with the decimal type. Using the == with decimal values is always dangerous. The decimal type is accurate (by default) to 28 places, which means you're trying to find an extremely good approximation for this problem, because only when it's correct to 28 decimals will (current_savings>downpayment or current_savings<downpayment) evaluate to False invoking your exit condition.

Basically, the issue that's causing your problem is that even when you eventually get the estimate of $1,000,000.0000000001, python says this is not equal to $1,000,000.0000000000, so it keeps going until it gets the next 0, but then it just adds another zero and so on. This will continue for a very very long time and in rare cases might never stop due to the fact that not all decimal numbers can be stored as binary numbers (1,000,000 is not among those cases).

So, how do we solve this? There are two options. The easiest one is to ignore cents and just cast your comparison values to int , this will make sure that any value off by a fraction of a dollar will be accepted. The other options is to create a range of accepted answers. Say for example, I'd like to save EXACTLY $1 million in those 36 months, but that isn't likely to happen. So, instead, I'll settle for any amount in the range $1,000,000.00 - $1,000,010.00 (for example). This way, we ensure that any guess that is way too high will get rejected and only a very limited amount of guesses are accepted.

Regardless of which route you go, it is generally a good idea to put the exit condition of an infinite loop to the top, this way you guarantee that it will always be evaluated.

My suggestion would be to write a function like this and use that for your condition to exit the loop (which you would place at the top):

def are_decimals_equal(a, b):
    accuracy = 0.0001
    return abs(a-b) < accuracy

This will consider 0.00009 (and all decimals less than that) to be equal to 0.0000.

Original

First off, just as a note, what you're doing is not called bisection, it's called binary search.

Now to the problem, you are never altering the value of month in your main loop. This means, as soon as current_savings>downpayment evaluates to False, your program will go into an infinite loop as none of the conditions after it could evaluate to True as month>=36 will always be False.

From what I can see, the second part of your conditions in the if/elif statements is unnecessary, your calSavings will always compute 36 months worth of savings, never more, never less. Thus, if you remove that condition from your if/elif statements, your program will eventually stop and at that point it should settle on the correct answer.

Lastly, the reason why you're seeing 0 as the output is your division at the end. If you do print(typeof(guess)) you will see it is an integer, 100 is also an integer, thus this division will result in some value like 0.3123 which will get truncated to 0 . Change your output to float(guess/100) and this will go away.

I hope it's okay for me to provide an answer to my own question here - though it's not a perfect answer.

The results the code produces seem plausible.

total_cost=1000000 #cost of house

portion_down_payment=0.25 #fraction of cost needed for downpayment on house
downpayment=total_cost*portion_down_payment

starting_annual_salary=float(input("Enter the starting salary: "))
monthly_salary=starting_annual_salary/12
low=0
high=1000
binary=0
month=1 #set as 1 because the first calculation will occur in month 1
guess=(low+high)//2
current_savings=0
tolerance=500

def calSavings(current_savings,monthly_salary,guess,month):
    while month<37:
        monthly_savings=int(monthly_salary*(guess/1000))
        current_savings+=monthly_savings
        month+=1
    return(current_savings)

current_savings=calSavings(current_savings,monthly_salary,guess,1)

while True:
    if abs(current_savings-downpayment)<=tolerance: #if the difference between the current savings and downpayment is less than $500
        # record the savings rate as the best guess
        print("The best savings rate is ",guess/10,"%, the amount saved was $",current_savings," in 36 months")
        break #break out of while loop
    elif (current_savings-downpayment)>tolerance: #if amount reached goes over 25% of $1m within 36 months
        #a lower savings rate should be the new guess
        high=guess #high=guess (old guess) and low=low (stays same) and update the guess
        binary=binary+1
        guess=(low+high)//2
        print("The guess was too high, so the new lower savings rate is",guess/10,"%. This is binary-search step",binary)
        current_savings=calSavings(0,monthly_salary,guess,1)
        continue #send new guess up to beginning of while loop to check if conditionals
    elif (downpayment-current_savings)>tolerance: #if amount does not come to within tolerance amount of 25% of $1m within 36 months
        low=guess #to make the guess higher, make low=guess (old guess) and high stay the same
        binary=binary+1
        guess=(low+high)//2
        print("guess is ",guess)
        if guess>=990: #check if the savings rate guess is getting too high
            print("Your wages are too low. You can't save up enough")
            break #exit the while loop because conditions will never be met
        print("The guess was too low, so the new higher savings rate is",guess/10,"%. This is binary-search step",binary)
        current_savings=calSavings(0,monthly_salary,guess,1)
        continue #send new guess up to beginning of while loop to check over the conditionals

The tolerance for an acceptable answer is within $500, but if I lower that to $50, I end up in a seemingly infinite loop again, where the guess and the low end up being the same. I am happy that I've made some apparent progress, but baffled that I can't lower the tolerance without it going haywire again.

BTW, I didn't want to seem like I ignored Nick's comments about making the variables into floats, but I explained why I worked in integers in my comment - does that seem correct?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM