3
\$\begingroup\$

Write a program to calculate the credit card balance after one year if a person only pays the minimum monthly payment required by the credit card company each month.

The following variables contain values as described below:

balance - the outstanding balance on the credit card

annualInterestRate - annual interest rate as a decimal

monthlyPaymentRate - minimum monthly payment rate as a decimal

For each month, calculate statements on the monthly payment and remaining balance. At the end of 12 months, print out the remaining balance. Be sure to print out no more than two decimal digits of accuracy - so print

Remaining balance: 813.41

instead of

Remaining balance: 813.4141998135

So your program only prints out one thing: the remaining balance at the end of the year in the format:

Remaining balance: 4784.0

A summary of the required math is found below:

\begin{align*} \text{Monthly interest rate} &= \frac{\text{Annual interest rate}}{12} \\ \text{Minimum monthly payment} &= \text{Minimum monthly payment rate} \times \text{Previous balance} \\ \text{Monthly unpaid balance} &= \text{Previous balance} \\ &- \text{Minimum monthly payment} \\ \text{Updated balance each month} &= \text{Monthly unpaid balance} \\ &+ (\text{Monthly interest rate} \times \text{Monthly unpaid balance}) \end{align*}

Code

def balances(initial_balance, annual_interest_rate, minimum_monthly_payment_rate):
    balance = initial_balance
    monthly_interest_rate = annual_interest_rate / 12

    for month in range(13):
        minimum_monthly_payment = balance * minimum_monthly_payment_rate

        monthly_unpaid_balance = balance - minimum_monthly_payment 

        yield {'month': month,
               'minimum_monthly_payment': minimum_monthly_payment,
               'balance': balance}

        balance = monthly_unpaid_balance + (monthly_unpaid_balance * monthly_interest_rate)

def main():
    *bs, remaining_balance = balances(balance, annualInterestRate, monthlyPaymentRate)
    print('Remaining Balance: {}'.format(round(remaining_balance['balance'], 2)))

# Testing data
balance = 484
annualInterestRate = .2
monthlyPaymentRate = .04

## ifname_main does not work in grader
if __name__ == '__main__':
    main()

Thanks in advance.

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$
  • Could use some PEP484 type hints
  • range(13) is awkward - there's no point in yielding the initial balance, so just yield adjusted balances and do it 12 times
  • No point in yielding the month, payment and balance; just yield the balance. Even if you needed to yield all three, a dictionary is not a great way to do it. Tuples are standard; or a dataclass is better-structured.
  • No need to call format or round. Just use .2f. Note that this contravenes the specification's Remaining balance: 4784.0 but that's dumb; this is a money quantity so the precision should be fixed.
  • Your balances is fine as a generator, but why limit it to 12 iterations? The more generic and useful implementation simply iterates forever. Use islice to pull 12 values, ignoring all but the last.

Suggested (iterative)

from itertools import islice
from typing import Iterable


def balances(
    initial_balance: float,
    annual_interest_rate: float,
    minimum_monthly_payment_rate: float,
) -> Iterable[float]:
    balance = initial_balance
    monthly_interest_rate = annual_interest_rate / 12

    while True:
        minimum_monthly_payment = balance * minimum_monthly_payment_rate
        monthly_unpaid_balance = balance - minimum_monthly_payment
        balance = monthly_unpaid_balance * (1 + monthly_interest_rate)
        yield balance


def main():
    for balance in islice(
        balances(
            initial_balance=484.00,
            annual_interest_rate=0.20,
            minimum_monthly_payment_rate=0.04,
        ),
        12,
    ):
        pass

    print(f'Remaining Balance: {balance:.2f}')


if __name__ == '__main__':
    main()

Simplifying the inner loop would replace it with

balance *= (1 - minimum_monthly_payment_rate) * (1 + monthly_interest_rate)

which is equivalent.

Suggested (compound)

Even better is to get rid of the loop entirely, and just use a power:

def compound(
    initial_balance: float,
    annual_interest_rate: float,
    minimum_monthly_payment_rate: float,
    months: int,
) -> float:
    monthly_interest_rate = annual_interest_rate / 12

    return initial_balance * (
        (1 - minimum_monthly_payment_rate) * (1 + monthly_interest_rate)
    ) ** months


def main():
    balance = compound(
        initial_balance=484.00,
        annual_interest_rate=0.20,
        minimum_monthly_payment_rate=0.04,
        months=12,
    )

    print(f'Remaining Balance: {balance:.2f}')


if __name__ == '__main__':
    main()
\$\endgroup\$
3
  • \$\begingroup\$ Wouldn't it be useless to yield negative balances? Maybe make it while balance >= 0 \$\endgroup\$ Jun 22, 2021 at 15:35
  • \$\begingroup\$ Possible. Add that if you want. There are some (non-credit-card) account types that would benefit from being able to represent both credit and debit states. \$\endgroup\$
    – Reinderien
    Jun 22, 2021 at 15:56
  • \$\begingroup\$ That said, do a little math and you'll find that the balance never goes to or below zero for a finite number of iterations. \$\endgroup\$
    – Reinderien
    Jun 22, 2021 at 16:58
1
\$\begingroup\$

The balances function is a bit long. The contents of the loop can be another function. This function shall be responsible to satisfy this requirement:

For each month, calculate statements on the monthly payment and remaining balance

Instead of yield-ing a structure with month, minimum_monthly_payment, and balance for each iteration of the loop, you can return at the end of the loop. Actually, month and minimum_monthly_payment is not used at all, so you may only return the remaining balance at the end of 12 months.

Since the balances function now returns only a single number, you can get rid of the complex code at main. Just get that number and print it.

After those changes, rename the balances method to something more meaningful.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.