使用功能方法在C#中均匀划分资金

时间:2011-05-04 00:45:22

标签: c#-4.0 functional-programming linq-to-objects

我有这两个值:

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,金额= 444.10m}
{InstallmentNumber = 3,Amount = 444.09m}

我正在寻找一种有趣的方式来创建我的3期。使用简单的LINQ to objects方法会很好。我最近一直试图更多地了解函数式编程,这似乎是一个相当不错的递归练习。我能想到的唯一正确的方法是使用传统的while或for循环...

2 个答案:

答案 0 :(得分:4)

这里没有很多“功能性”。我会像这样解决问题:

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)并用一些初始值(空列表,起始值)引导它。使用match expressions设置终结符(如果installmentCount为0)到目前为止返回列表。否则,它会计算付款金额并调用递归方法,将新的分期付款添加到列表的前面,从剩余的金额中减去付款金额,然后减去计数。

这实际上反过来构建列表(每次都添加到前面),所以我们扔掉了额外的便士(truncate)并最终赶上了我们,所以便士舍入按预期工作。这显然比上面的加/减代码更加数学密集,因为我们在每次迭代中进行除法和乘法。但它是完全递归的,并利用尾递归,所以我们永远不会用完堆栈。

C#的问题在于你需要一系列的分期付款和递归,并且在C#中没有惯用的内置结构。这里我使用的是F#的列表,它是不可变的,O(1)操作是前置的。

您可以使用Reactive Extensions中的Scan()方法构建一些内容,以便将状态从一次实例传递到另一个实例。

答案 1 :(得分:1)

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);