Здесь не так уж много "функционального". Я бы подошел к проблеме так:
var pennies = (totalAmountDue * 100) % installmentCount;
var monthlyPayment = totalAmountDue / installmentCount;
var installments = from installment in Enumerable.Range(1, installmentCount)
let amount = monthlyPayment + (Math.Max(pennies--, 0m) / 100)
select new Installment(installment, amount);
Возможно, вам удастся найти что-то, где вы постоянно вычитаете предыдущий платеж из общей суммы и выполняете округление до ближайшей копейки. В F # (C # слишком многословно для этого) это может быть что-то вроде:
let calculatePayments totalAmountDue installmentCount =
let rec getPayments l (amountLeft:decimal) = function
| 0 -> l
| count -> let paymentAmount =
(truncate (amountLeft / (decimal)count * 100m)) / 100m
getPayments (new Installment(count, paymentAmount)::l)
(amountLeft - paymentAmount)
(count - 1)
getPayments [] totalAmountDue installmentCount
Для тех, кто не знаком с F #, этот код создает рекурсивную функцию (getPayments
) и загружает ее с некоторыми начальными значениями (пустой список, начальные значения). Используя выражения соответствия , он устанавливает терминатор (если installmentCount
равен 0), возвращая список до сих пор. В противном случае он вычисляет сумму платежа и вызывает рекурсивный метод, добавляя новый взнос в начало списка, вычитая сумму платежа из оставшейся суммы и вычитая количество.
Это фактически строит список в обратном порядке (добавляя каждый раз вперед), поэтому мы выбрасываем лишние копейки (truncate
), и в конечном итоге он догоняет нас, поэтому округление пенни работает, как и ожидалось. Это, очевидно, более сложная математика, чем приведенный выше код сложения / вычитания, поскольку мы делим и умножаем на каждой итерации. Но он полностью рекурсивный и использует преимущества хвостовой рекурсии, поэтому мы никогда не выберемся из стека.
Проблема с C # здесь в том, что вам нужна последовательность платежей и рекурсия, и нет никакой идиоматичной встроенной структуры для этого в C #. Здесь я использую список F #, который является неизменным, и операцию O (1) для добавления.
Возможно, вы могли бы создать что-то, используя метод Scan()
в Reactive Extensions для передачи состояния из одного экземпляра в другой.