【问题标题】:Python: Using recursion to find lowest fixed monthly payment in multiples of 10Python:使用递归以 10 的倍数找到最低的固定每月付款
【发布时间】:2018-05-02 16:17:01
【问题描述】:

每次我在 edX.org 上的 Python 课程中,我都得到了 11 或 12 的正确答案,但在讨论中没有得到任何人的帮助,因为没有人可以真正在那里发布任何代码(没有真正的帮助)并且课程中似乎没有任何可用的支持人员可以与之交谈,我会为此付费,所以我在这里发帖。我正要花钱请人来辅导我,但现在没有人可用,而且我承受着一定的压力,要在 12 月之前完成这门课程以完成我的工作。

这是作业:

现在编写一个程序,计算在 12 个月内还清信用卡余额所需的最低每月固定还款额。固定的每月付款是指一个数字,每个月都不会改变,而是每个月都会支付的固定金额。

在这个问题中,我们不会处理最低月付款率。

以下变量包含如下所述的值:

balance - 信用卡上的未结余额

annualInterestRate - 年利率小数

程序应打印出一行:在 1 年内偿还所有债务的最低每月付款,例如:

最低付款:180
假设按月末余额(当月还款后)按月复利。每月付款必须是 10 美元的倍数,并且所有月份都相同。请注意,使用此付款方案,余额可能会变为负数,这没关系。所需数学的摘要如下:

月利率=(年利率)/12.0
每月未付余额 =(以前的余额)-(每月最低固定还款额)
每月更新余额=(每月未付余额)+(每月利率x每月未付余额)

这是我的代码:

#! /usr/bin/python3.6

from math import ceil

def roundup(x):
    return int(ceil(x / 10.0) * 10)

def getFixedPayment(balance,annualInterestRate,counter=12):

    totalLoan = balance + (balance*annualInterestRate)
    monthlyPayment = roundup(totalLoan/12.0)
    newBalance = totalLoan - monthlyPayment
    if counter < 12:
        newPayment = newBalance / counter + 1
    else:
        newPayment = newBalance / counter

    if counter == 1:
        return roundup(newPayment/12)
    else:
        return getFixedPayment(balance,annualInterestRate,counter-1)


#balance = 3329
#annualInterestRate = 0.2
print('Lowest Payment: ' + str(getFixedPayment(balance,annualInterestRate)))

这是测试结果:(我这里有 15 个,所以你可能能够识别出我看不到的模式。标记为“错误”的是我得到的错误)

Test Case 1
balance = 3329; annualInterestRate = 0.2
Output:
Lowest Payment: 310

Test Case 2
balance = 4773; annualInterestRate = 0.2
Output:
Lowest Payment: 440

Test Case 3
balance = 3926; annualInterestRate = 0.2
Output:
Lowest Payment: 360

Randomized Test Case 1
balance = 265; annualInterestRate = 0.18
Output:
Lowest Payment: 30

Randomized Test Case 2
balance = 263; annualInterestRate = 0.18
Output:
Lowest Payment: 30

Randomized Test Case 3
balance = 317; annualInterestRate = 0.25
Output:
Lowest Payment: 30

Randomized Test Case 4
balance = 720; annualInterestRate = 0.2
Output:
Lowest Payment: 70

Randomized Test Case 5
balance = 4284; annualInterestRate = 0.2
Output:
Lowest Payment: 400

Randomized Test Case 6
balance = 3834; annualInterestRate = 0.15
Your output:
Lowest Payment: 340


*** ERROR: Expected Lowest Payment: 350

, but got Lowest Payment: 340

 ***
Correct output:
Lowest Payment: 350

Randomized Test Case 7
balance = 3045; annualInterestRate = 0.18
Output:
Lowest Payment: 280

Randomized Test Case 8
balance = 4461; annualInterestRate = 0.2
Output:
Lowest Payment: 410

Randomized Test Case 9
balance = 4657; annualInterestRate = 0.04
Your output:
Lowest Payment: 370


*** ERROR: Expected Lowest Payment: 400

, but got Lowest Payment: 370

 ***
Correct output:
Lowest Payment: 400

Randomized Test Case 10
balance = 3395; annualInterestRate = 0.2
Your output:
Lowest Payment: 320


*** ERROR: Expected Lowest Payment: 310

, but got Lowest Payment: 320

 ***
Correct output:
Lowest Payment: 310

Randomized Test Case 11
balance = 4045; annualInterestRate = 0.15
Your output:
Lowest Payment: 360


*** ERROR: Expected Lowest Payment: 370

, but got Lowest Payment: 360

 ***
Correct output:
Lowest Payment: 370

Randomized Test Case 12
balance = 3963; annualInterestRate = 0.18
Output:
Lowest Payment: 360

【问题讨论】:

  • 当你手工处理错误案例时,你得到了正确的答案吗?
  • 就像评分机一样,时好时坏。
  • 你对你知道答案应该是什么感到满意吗?
  • 嗨卡洛斯,不,不是真的。那是我的结果的打印输出。除了 1st 3 的结果,这些都是课程评分者的随机结果。
  • 我发现这个问题是如何与规范、自己的尝试和一整套测试用例一起出现的,但仍然有人想为“为什么这段代码不起作用?”而关闭它。原因。

标签: python recursion


【解决方案1】:

一个问题是:

假设利息按月复利

但你计算:

totalLoan = balance + (balance*annualInterestRate)

您无法以这种方式计算 totalLoan。您应该考虑compound interest,并计算每个月的未付余额。

天真的方法

这是一个例子。请注意,您的递归可以替换为一个简单的循环:

def balance_after_a_year(balance, monthly_payment, annual_interest_rate):
    monthly_interest_rate = annual_interest_rate / 12.0
    for month in range(12):
        balance = (balance - monthly_payment) * (1 + monthly_interest_rate)
    return balance

print(balance_after_a_year(3834, 340, 0.15))
# 23.153402858591026
print(balance_after_a_year(3834, 350, 0.15))
# -107.05775649703746

您现在可以以天真的方式使用此功能。只需以 10 步从 0 迭代到 balance,然后选择一年后余额为负的第一个值:

def getFixedPayment(b,r):
    return next(m for m in range(0, b, 10) if balance_after_a_year(b, m, r) <= 0)
print(getFixedPayment(3834, 0.15))
# 350

此方法已通过@janos提供的doctest验证。

有更有效的方法,但这个简单明了。

每月支付公式

您可以直接使用monthly payment formula计算每月付款:

def getFixedPayment(b,r):
    m = r / 12.0
    return m * b / (1 - (1 + m)**-12)
print(getFixedPayment(3834, 0.15))
# 346.05036953133276

不需要任何循环!

【讨论】:

  • 是的,我同意。迭代非常好。该课程需要递归,因为之前的课程是关于递归的。感谢您添加有关计算复利的观点。我很感激。我很困惑。上一个问题让我找到了最低月供。这个问题说“在这个问题中,我们不会处理最低每月付款率。”,所以这让我有点失望。再次感谢您。
  • @Debug255:说实话,这句话我没看懂。也许他们的意思是这不完全是每月最低付款额,而是四舍五入?递归可能是一个非常强大的工具,但这里真的不需要它。
  • 我 100% 同意你的看法。据我了解,在 Python 中,递归使用堆栈,而迭代使用堆。那是对的吗?如果是这样,迭代对系统来说也更有效。
【解决方案2】:

虽然函数是递归的(它调用自己), 它以一种毫无意义、无效的方式这样做。

考虑counter 的任何值大于 1 时会发生什么:

def getFixedPayment(balance, annualInterestRate, counter=12):
    totalLoan = balance + (balance*annualInterestRate)
    monthlyPayment = roundup(totalLoan/12.0)
    newBalance = totalLoan - monthlyPayment

    if counter < 12:
        newPayment = newBalance / counter + 1
    else:
        newPayment = newBalance / counter

    if counter == 1:
        return roundup(newPayment/12)
    else:
        return getFixedPayment(balance,annualInterestRate,counter-1)
        #                                                 ^^^^^^^^^
        #                                                 the only change!

counter &gt; 1,函数“做一些计算”, 但没关系,因为最后它只是用counter - 1 调用自己。 所以对于counter = 12的起始值, 该函数将重复调用自身 11 次。 因此,它简化为:

def getFixedPayment(balance, annualInterestRate):
    totalLoan = balance + (balance*annualInterestRate)
    monthlyPayment = roundup(totalLoan/12.0)
    newBalance = totalLoan - monthlyPayment

    newPayment = newBalance / counter + 1

    return roundup(newPayment/12)

这可能给出正确的答案吗?不太可能。

考虑还款方式。 让我们举一个更简单的例子,分三步偿还, 并考虑这种替代符号,以便编写更简单:

  • 让我们致电T 支付总金额今天
  • 我们称r为年利率的乘数,即年利率为4%,这个值为1.04,所以我们可以得到T * r需要支付的金额。李>
  • 让我们致电x 目标每月固定付款

本月支付x后,还剩多少?

(T - x) * r

即我们本月支付x,剩余金额为T - x, 根据描述,我们需要乘以年利率。

下个月,我们再次支付x,所以剩下要偿还的将是:

((T - x) * r - x) * r

在第三个月,我们支付了最后一笔款项x

练习的目标是找到x,这样:

((T - x) * r - x) * r - x <= 0

让我们重新组织这个方程来找到x的值:

((T - x) * r - x) * r <= x

(T - x) * r - x <= x / r

(T - x) * r <= x / r + x

T - x <= x / r / r + x / r

T <= x / r / r + x / r + x

T <= x * (1 / r / r + 1 / r + 1)

T / (1 / r / r + 1 / r + 1) <= x

在这个例子中,我们在 3 个月内偿还。 您可以通过添加一个月来查看此公式的变化:

T / (1 / r / r / r + 1 / r / r + 1 / r + 1) <= x

也就是说,给定T / m,加一个月意味着T / (m / r + 1)

现在看起来我们可以使用递归逻辑了。

在不完全破坏你的练习的情况下, 这是解决方案的模板,您只需要找出... 的正确值。祝你好运!

def getFixedPayment(balance, annual_interest_rate, counter=12, interest=...):

    if counter == 1:
        return roundup(balance / interest)

    monthly_interest_rate = annual_interest_rate / 12
    r = 1 + monthly_interest_rate

    return getFixedPayment(balance, annual_interest_rate, counter - 1, ...)

这里有一些文档测试来验证您的解决方案。 如果您的程序位于名为calc.py 的文件中, 您可以使用python -mdoctest calc.py 运行文档测试。 它将打印失败测试用例的摘要(如果有), 否则如果一切正常,它就不会打印任何内容。

def getFixedPayment(balance, annual_interest_rate, counter=12, interest=...):
    """
    >>> getFixedPayment(3329, 0.2)
    310

    >>> getFixedPayment(4773, 0.2)
    440

    >>> getFixedPayment(3926, 0.2)
    360

    >>> getFixedPayment(265, 0.18)
    30

    >>> getFixedPayment(263, 0.18)
    30

    >>> getFixedPayment(317, 0.25)
    30

    >>> getFixedPayment(720, 0.2)
    70

    >>> getFixedPayment(4284, 0.2)
    400

    >>> getFixedPayment(3834, 0.15)
    350

    >>> getFixedPayment(3045, 0.18)
    280

    >>> getFixedPayment(4461, 0.2)
    410

    >>> getFixedPayment(4657, 0.04)
    400

    >>> getFixedPayment(3395, 0.2)
    310

    >>> getFixedPayment(4045, 0.15)
    370

    >>> getFixedPayment(3963, 0.18)
    360

    """

    if counter == 1:
        return roundup(balance / interest)

    monthly_interest_rate = annual_interest_rate / 12
    r = 1 + monthly_interest_rate

    return getFixedPayment(balance, annual_interest_rate, counter - 1, ...)

【讨论】:

  • 感谢您的 doctest,我可以用它来验证我的答案。
  • 不知道。非常便利。在我的 py 文件中,我使用 if 语句手动编写了我自己的 doctest。这样效率更高。
猜你喜欢
  • 1970-01-01
  • 2019-05-05
  • 2017-12-06
  • 2015-04-13
  • 1970-01-01
  • 1970-01-01
  • 2015-08-02
  • 2016-01-25
  • 2011-01-28
相关资源
最近更新 更多