This is the my solution to a coding challenge in Geektrust. The question is linked here.
A shortened version would be as follows.
The aim is to simulate a marketplace for banks to lend money to borrowers and receive payments for the loans. It takes as input:
- The bank name, borrower name, principal, interest and term.
- Lump sum payments if any.
- Given the bank name, borrower name, and EMI number, the program should print the total amount paid by the user (including the EMI number mentioned) and the remaining number of EMIs.
The output should be
- Amount paid so far, and number of EMIs remaining for the user with the bank
These are the commands the program takes as input
- LOAN BANK_NAME BORROWER_NAME PRINCIPAL NO_OF_YEARS RATE_OF_INTEREST
- PAYMENT BANK_NAME BORROWER_NAME LUMP_SUM_AMOUNT EMI_NO
- BALANCE BANK_NAME BORROWER_NAME AMOUNT_PAID EMI_NO
This is my solution to the problem
import math
from sys import argv
LOAN = "LOAN"
PAYMENT = "PAYMENT"
BALANCE = "BALANCE"
MONTHS_IN_A_YEAR = 12
HUNDRED = 100
def round_to_whole_no(n):
return math.ceil(n)
class Bank:
def __init__(self, name):
self.name = name
class BankLoan:
def __init__(self, **kwargs):
self.bank = kwargs['bank']
self.principal = kwargs['principal']
self.rate_of_interest = kwargs['rate_of_interest']
self.no_of_years = kwargs['no_of_years']
self.borrower = kwargs['borrower']
def interest(self):
return self.principal * self.no_of_years * (self.rate_of_interest / HUNDRED)
def total_amount(self):
return self.interest() + self.principal
def emi_amount(self):
return round_to_whole_no(self.total_amount() / (self.no_of_years * MONTHS_IN_A_YEAR))
def no_of_emi(self):
return self.no_of_years * MONTHS_IN_A_YEAR
class Borrower:
def __init__(self, name):
self.name = name
class LedgerEntry:
def __init__(self, bank_loan=None, payment=None):
self.bank_loan = bank_loan
self.payment = payment
def add_payment(self, payment):
self.payment = payment
def balance(self):
if self.payment is None:
return self.bank_loan.total_amount()
else:
return self.bank_loan.total_amount() - self.payment.lump_sum_amount
def amount_paid(self, emi_no):
if self.payment is None:
return emi_no * self.bank_loan.emi_amount()
else:
if emi_no >= self.payment.emi_no:
return self.payment.lump_sum_amount +\
(self.balance()
if ((emi_no * self.bank_loan.emi_amount()) + self.payment.lump_sum_amount) >
self.bank_loan.total_amount()
else emi_no * self.bank_loan.emi_amount())
else:
return emi_no * self.bank_loan.emi_amount()
def no_of_emi_left(self, emi_no):
if self.payment is None:
return self.bank_loan.no_of_emi() - emi_no
else:
remaining_principal = self.bank_loan.total_amount() - self.amount_paid(emi_no=emi_no)
remaining_emi = round_to_whole_no(remaining_principal / self.bank_loan.emi_amount())
return remaining_emi
class Ledger(dict):
def __init__(self):
super().__init__()
def add_entry_to_ledger(self, bank_name, borrower_name, ledger_entry):
self[bank_name] = {}
self[bank_name][borrower_name] = ledger_entry
class Payment:
def __init__(self):
pass
def __init__(self, **kwargs):
self.bank_loan = kwargs['bank_loan']
self.lump_sum_amount = kwargs['lump_sum_amount']
self.emi_no = kwargs['emi_no']
def main():
ledger = Ledger()
if len(argv) != 2:
raise Exception("File path not entered")
file_path = argv[1]
f = open(file_path, 'r')
lines = f.readlines()
for line in lines:
if line.startswith(LOAN):
bank_name, borrower_name, principal, no_of_years, rate_of_interest = line.split()[1:]
bank = Bank(name=bank_name)
borrower = Borrower(name=borrower_name)
bank_loan = BankLoan(bank=bank,
borrower=borrower,
principal=float(principal),
no_of_years=float(no_of_years),
rate_of_interest=float(rate_of_interest))
ledger_entry = LedgerEntry(bank_loan=bank_loan)
ledger.add_entry_to_ledger(bank_name=bank_name, borrower_name=borrower_name,
ledger_entry=ledger_entry)
if line.startswith(PAYMENT):
bank_name, borrower_name, lump_sum_amount, emi_no = line.split()[1:]
ledger_entry = ledger[bank_name][borrower_name]
payment = Payment(bank_loan=ledger_entry.bank_loan,
lump_sum_amount=float(lump_sum_amount),
emi_no=float(emi_no))
ledger_entry.add_payment(payment)
ledger[bank_name][borrower_name] = ledger_entry
if line.startswith(BALANCE):
bank_name, borrower_name, emi_no = line.split()[1:]
ledger_entry = ledger[bank_name][borrower_name]
amount_paid = int(ledger_entry.amount_paid(emi_no=float(emi_no)))
no_of_emis_left = int(ledger_entry.no_of_emi_left(emi_no=float(emi_no)))
print(bank_name, borrower_name, str(amount_paid), str(no_of_emis_left))
if __name__ == "__main__":
main()
The automated review of the code by Geektrust has reported the code to have poor Object Oriented modelling and that it breaks encapsulation.