【问题标题】:Bisect Search to choose best savings rate平分搜索以选择最佳储蓄率
【发布时间】:2018-10-31 17:43:16
【问题描述】:

您好,我需要一些帮助来解决这个设置为 MIT OCW 计算机科学和 Python 课程的问题之一的问题。我知道有人问过类似的问题,我也找到了有用的帖子,例如 Bisection search code doesnt work,但我仍然卡住了!

我已经为这个问题苦苦挣扎了很多天,并试图以不同的方式解决它,但都失败了。如果可能的话,有人可以暗示我哪里出错了,而不是告诉我答案。我想通过一些帮助自己解决这个问题。

供参考,问题是C部分,这里: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

由于我一直在努力,我已将这项任务分解为一个总体目标,然后分解为解决问题的步骤。

目标:尝试找到在 36 个月内支付 100 万美元房屋首付的最佳储蓄率##解决问题的步骤:
1) 猜测储蓄率,即 0 和 1000 的平均值
2) 计算 36 个月后的增长情况
3a) 如果在 36 个月内达到的金额超过 100 万美元的 25%,那么较低的储蓄率应该是新的猜测
...max=guess(旧猜测)和 min=0 并更新猜测(高低的平均值)
...使用新的猜测运行步骤 2 中的计算
3b) 如果金额在 36 个月内没有达到 100 万美元的 25%,那么更高的储蓄率应该是新的猜测
...min=guess(旧猜测)并更新猜测(高低的平均值) ...使用新的猜测运行步骤 2 中的计算
3c) 如果金额在第 36 个月期限内达到 100 万美元的 25%,则退出并记录储蓄率作为最佳猜测。
为简单起见:假设没有利息并假设工资保持不变

所以这是我目前正在努力解决这个问题的代码。 (它导致“猜测”变量趋于 0,然后无限循环)

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

我知道其他人也问过类似的问题(我已经查看了这些答案,但仍然没有解决我的问题),但不仅仅是一个答案,我需要关于如何解决这个问题的帮助。

【问题讨论】:

    标签: python search binary-search bisection


    【解决方案1】:

    更新

    你的循环没有停止的原因是你没有给它足够的时间。您忘记的是您正在处理 decimal 类型。将==decimal 值一起使用总是很危险的。 decimal 类型准确(默认)到 28 位,这意味着您正在尝试为这个问题找到一个非常很好的近似值,因为只有当它正确到小数点后 28 位时,(current_savings&gt;downpayment or current_savings&lt;downpayment)评估为 False 调用您的退出条件。

    基本上,导致你的问题的问题是,即使你最终得到 1,000,000.0000000001 美元的估计值,python 说这不等于 1,000,000.0000000000 美元,所以它会一直运行,直到它得到下一个 0,但它只是添加了另一个零等等。这将持续很长时间,并且在极少数情况下可能永远不会停止,因为并非所有十进制数都可以存储为二进制数(1,000,000 不在这些情况中)。

    那么,我们如何解决这个问题?有两种选择。最简单的方法是忽略美分,只需将您的比较值转换为int,这将确保接受任何相差一美元的值。其他选项是创建一系列可接受的答案。例如,我想在这 36 个月内节省 100 万美元,但这不太可能发生。因此,相反,我将结算 1,000,000.00 美元 - 1,000,010.00 美元(例如)范围内的任何金额。这样,我们确保任何过高的猜测都会被拒绝,并且只接受非常有限的猜测。

    无论你走哪条路,通常最好将无限循环的退出条件放在顶部,这样你就可以保证它总是会被评估。

    我的建议是编写一个这样的函数,并使用它作为你的条件来退出循环(你将把它放在顶部):

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

    这将认为 0.00009(以及小于该值的所有小数)等于 0.0000。

    原创

    首先,请注意,您所做的不是二分法,而是二分搜索。

    现在的问题是,您永远不会在主循环中更改月份的值。这意味着,一旦 current_savings&gt;downpayment 评估为 False,您的程序将进入无限循环,因为在它可以评估为 True 之后没有任何条件,因为 month&gt;=36 将始终为 False。

    据我所知,您在 if/elif 语句中的条件的第二部分是不必要的,您的 calSavings 将始终计算 36 个月的节省,不会更多,也不会更少。因此,如果您从 if/elif 语句中删除该条件,您的程序最终将停止,此时它应该确定正确的答案。

    最后,您看到0 作为输出的原因是您最后的部门。如果你执行print(typeof(guess)),你会看到它是一个整数,100 也是一个整数,因此这个除法会产生一些像0.3123 这样的值,它会被截断为0。将您的输出更改为float(guess/100),这将消失。

    【讨论】:

    • 非常感谢您帮助我!我会回去应用你的建议并在这里反馈。
    • 这里只关注 While 循环中的 If 条件。我认为 calSavings 仅运行了 36 个月,因此我删除了与月份相关的每个 If 条件的第二部分。在 If 条件中,我重置了月份,month=1,当前储蓄,current_savings=0。当程序“继续”时,它不应该返回到 while 循环的开始,然后新的“猜测”并重新计算 current_savings 吗?它似乎没有,因为它处于无限循环中。我真的错过了这里的逻辑!
    • (1) 您根本不需要月份变量,只需将其删除即可。 (2) Continue 像你想象的那样工作,它将运行(无限)循环的下一次迭代,但是,如果上面的代码是完整的,你也不需要它,因为下一步将是下一次迭代.
    • 我会更新我关于为什么它没有停止的答案。
    • 我还注意到您的代码中存在另一个问题。这条线monthly_savings=monthly_salary*(guess/1000) 应该真的是monthly_savings=monthly_salary*(float(guess)/1000.0),以确保你没有得到一个整数值。同样,为了安全起见,声明 low = 0.0high = 1000.0current_savings = 0.0
    【解决方案2】:

    我希望我可以在这里为我自己的问题提供答案 - 尽管这不是一个完美的答案。

    代码产生的结果似乎是合理的。

    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
    

    可接受答案的容差在 500 美元以内,但如果我将其降低到 50 美元,我最终会再次陷入看似无限的循环,其中猜测和低端是相同的。我很高兴我已经取得了一些明显的进步,但很困惑我不能降低容忍度,否则它会再次失控。

    顺便说一句,我不想​​看起来好像我忽略了尼克关于将变量转换为浮点数的 cmets,但我在评论中解释了为什么我使用整数工作 - 这看起来正确吗?

    【讨论】:

      猜你喜欢
      • 2017-12-23
      • 1970-01-01
      • 1970-01-01
      • 2014-09-08
      • 1970-01-01
      • 1970-01-01
      • 2021-10-16
      • 1970-01-01
      • 2017-10-28
      相关资源
      最近更新 更多