Разделите деньги равномерно в C #, используя функциональный подход - PullRequest
1 голос
/ 04 мая 2011

У меня есть эти 2 значения:

decimal totalAmountDue = 1332.29m;
short installmentCount = 3;

Я хочу создать 3 взноса, у которых есть четная сумма, основанная на totalAmountDue (дополнительные пенни применяются, начиная с наименьшего номера платежа, переходящего к наибольшему номеру платежа), используя этот класс:

public class Installment
{
   public Installment( short installmentNumber, decimal amount )
   {
     InstallmentNumber = installmentNumber;
     Amount = amount;
   }

   public short InstallmentNumber { get; private set; }
   public decimal Amount { get; private set; }
}

Рассрочка должна быть следующей:

{InstallmentNumber = 1, Amount = 444.10m}
{InstallmentNumber = 2, Amount = 444.10m}
{InstallmentNumber = 3, Amount = 444.09m}

Я ищу интересный способ создания моих 3-х частей. Было бы неплохо использовать простой метод LINQ to objects. В последнее время я пытался понять больше о функциональном программировании, и кажется, что это может быть довольно хорошим упражнением в рекурсии. Единственный достойный способ, которым я могу думать об этом, - это использовать традиционный цикл while или for на данный момент ...

Ответы [ 2 ]

4 голосов
/ 04 мая 2011

Здесь не так уж много "функционального". Я бы подошел к проблеме так:

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 для передачи состояния из одного экземпляра в другой.

1 голос
/ 04 мая 2011

Talljoe, я думаю, что вы толкаете меня в правильном направлении.Этот код ниже, кажется, работает.Я должен был переключиться, как работает математика пенни, но это выглядит довольно хорошо (я думаю)

decimal totalAmountDue = 1332.29m;
short installmentCount = 8;

var pennies = (totalAmountDue * 100) % installmentCount;
var monthlyPayment = Math.Floor(totalAmountDue / installmentCount * 100);

var installments = from installmentNumber in Enumerable.Range(1, installmentCount) 
                   let extraPenny = pennies-- > 0 ? 1 : 0
                   let amount =  (monthlyPayment + extraPenny) / 100
                   select new Installment(installmentNumber, amount);
...