累加算法结构

Accumulation algorithm structure

我有一个 table 可以找到与帐户相关的交易。 此 table 包含借方和贷方交易。

我需要帮助构建一个算法来计算帐户是否在特定时刻从未达到定义的值(假设 1000)。

让我们看看以下示例:

ID     Date             Value        Type    Account
----   -------------    ----------   -----   -------
1      2015-07-23       100.00       C       1
2      2015-07-28       350.00       C       1
3      2015-08-14       250.00       C       1
4      2015-08-30       180.00       C       1
5      2015-09-22       230.00       C       1
6      2015-09-28       230.00       D       1

在这种情况下,第一笔借记交易发生在余额为 1110.00 时。所以即使现在余额不足1000.00我也需要考虑这个账户

ID     Date             Value        Type    Account
----   -------------    ----------   -----   ---------
1      2015-07-23       190.00       C       2
2      2015-07-28       350.00       C       2
3      2015-08-14       450.00       C       2
4      2015-08-30       100.00       D       2
5      2015-09-22       100.00       C       2

在这种情况下,有一项借记交易在达到 1000.00 之前减少了余额。所以我不应该考虑这个账号。

是否有任何通用且简单的方法来进行此计算?

谢谢!

编辑:根据评论,这是我目前所拥有的:

decimal counter = 0;
bool hasBonus = false;
foreach ( var tx in txList ) {
    if ( tx.TransactionType == TransactionType.C ) {
        counter += tx.Value;
    }
    else if ( tx.TransactionType == TransactionType.D ) {
        counter -= tx.Value;
    }
    if ( counter >= 1000M ) {
        hasBonus = true;
    }
}

假设您有一笔交易 class

public enum TransactionType 
  {
    C,
    D
  }

public class Transaction
{
    public int Id {get; set;}
    public DateTime Date{get;set;}
    public double Value{get;set;}
    public TransactionType Type{get;set;}
    public int Account{get;set;}
}

如您所说,您是从数据库中获取它们的,因此该部分已被覆盖。你获得了 IEnumerable<Transaction>。使用以下函数可以解决问题:

public bool AccountIsGood(IEnumerable<Transaction> dbTransactions)
{
    var transactions = dbTransactions.OrderBy(t => t.Date).ToList();
    var sum = 0;
    foreach(var tran in transactions)
    {

       if(tran.Type = TransactionType.D)
       {
          return false;
       }
       sum += tran.Value;

       if(sum > 1000)
       {
         return true;
       }
   }
  return false;
}

Edit:C# 中的一个更优化的解决方案是,如果您可以使用下面的代码传递 IQueryable<Transaction> 而不是 IEnumarable<Transaction>,您可以拆分批量交易:

public bool AccountIsGood(IQueryable<Transaction> dbTransactions)
{
    var transactions = dbTransactions.OrderBy(t => t.Date);
   // transactions is now and OrderedQueryable
    var sum = 0M;
    var totalTrans = transactions.Count();
    var skip = 0;
    while(skip < totalTrans)
    {
       foreach(var tran in transactions.Skip(skip).Take(100).ToList())
       {

          if(tran.Type = TransactionType.D)
          {
             sum -= tran.Value;
          }
          else
          {
             sum += tran.Value;
          }

          if(sum > 1000M)
          {
            return true;
         }
       }
     }
     skip += 100;
   }
 return false;
}

一个更好的解决方案是,如果您可以将其移动到数据库中,从而避免一遍又一遍地访问数据库

这是一个使用 LINQ 的解决方案:

var transactions = new[]
                    {
                        new { Value = 100.0, IsCredit = true, Account = 1 },
                        new { Value = 350.0, IsCredit = true, Account = 1 },
                        new { Value = 250.0, IsCredit = true, Account = 1 },
                        new { Value = 180.0, IsCredit = true, Account = 1 },
                        new { Value = 230.0, IsCredit = true, Account = 1 },
                        new { Value = 230.0, IsCredit = false, Account = 1 },
                        new { Value = 190.0, IsCredit = true, Account = 2 },
                        new { Value = 350.0, IsCredit = true, Account = 2 },
                        new { Value = 450.0, IsCredit = true, Account = 2 },
                        new { Value = 100.0, IsCredit = false, Account = 2 },
                        new { Value = 100.0, IsCredit = true, Account = 2 },
                    };

var bonusStatusOfAccounts = transactions.GroupBy(
    t => t.Account,
    t => t,
    (account, accountTransactions) =>
    new
        {
            Account = account,
            HasBonus = accountTransactions.Aggregate(
                new { AccountBalance = 0.0, HasBonus = false },
                (state, t) =>
                    {
                        var newBalance = state.AccountBalance + (t.IsCredit ? t.Value : -t.Value);
                        return new
                            {
                                AccountBalance = newBalance,
                                HasBonus = state.HasBonus || newBalance >= 1000
                            };
                    },
                state => state.HasBonus)
        }).ToList();

通过最初按账户对交易进行分组,然后我们为每个账户的交易创建一个 IEnumerable,它具有足够的信息来确定 HasBonus 是否应该为真。

Aggregate() 的一般形式采用三个参数:

  1. 初始状态(在这种情况下,账户余额最初为零且 HasBonus 为 false)

  2. 委托给"add"一笔交易到这个状态(这里我计算新余额并设置HasBonus,如果它>=1000)

  3. 委托获取最终状态并从中得到我们想要的答案(这里只是通过获取 HasBonus 标志)

通过调整第二个委托中的逻辑,您可以准确控制在何种条件下授予奖金。

您的代码看起来可以很好地解决您的问题。但是,在您的循环中,您应该在达到平衡阈值后立即中断,以避免多余的计算:

if ( counter >= 1000M ) {
    hasBonus = true;
    // Stop iterating through transactions.
    break;
}

以良好的性能解决问题的真正关键在于读取交易数据的方式以及让其他组件访问它的方式。

确保您 return 作为 Enumerable 进行交易,并使用 yield return ... 到 return 来自读取循环的单个交易。

当你使用Entity Framework时只要不执行ToList()ToArray()Count()之类的就不用担心这个类似于在您的代码中较早的地方具体化事务集合。

Is there any general and simply way to make this calculation?

有了这个模型,没有。

IMO,唯一可以改进的是可用性

假设模型是这样的

public enum TransactionType { Credit, Debit }

public class Transaction
{
    public int ID { get; set; }
    public DateTime Date { get; set; }
    public decimal Value { get; set; }
    public TransactionType Type { get; set; }
    public bool IsCredit {  get { return Type == TransactionType.Credit; } }
    public int Account { get; set; }
}

我会把计算放在这样的辅助函数中

public static class TransactionUtils
{
    public static IEnumerable<KeyValuePair<Transaction, decimal>> GetCreditInfo(this IEnumerable<Transaction> accountTransactions)
    {
        decimal credit = 0;
        return from t in accountTransactions
               orderby t.Date, t.ID
               select new KeyValuePair<Transaction, decimal>(t, credit += t.IsCredit ? t.Value : -t.Value);
    }
}

现在 LINQ 查询可用于回答不同的问题,包括来自 post 的原始问题。

例如,让我们拿你的样本数据

var transactions = new List<Transaction>
{
    new Transaction { ID = 1, Date = new DateTime(2015, 07, 23), Value = 100, Type = TransactionType.Credit, Account = 1 },
    new Transaction { ID = 2, Date = new DateTime(2015, 07, 28), Value = 350, Type = TransactionType.Credit, Account = 1 },
    new Transaction { ID = 3, Date = new DateTime(2015, 08, 14), Value = 250, Type = TransactionType.Credit, Account = 1 },
    new Transaction { ID = 4, Date = new DateTime(2015, 08, 30), Value = 180, Type = TransactionType.Credit, Account = 1 },
    new Transaction { ID = 5, Date = new DateTime(2015, 09, 22), Value = 230, Type = TransactionType.Credit, Account = 1 },
    new Transaction { ID = 6, Date = new DateTime(2015, 09, 28), Value = 230, Type = TransactionType.Debit, Account = 1 },

    new Transaction { ID = 1, Date = new DateTime(2015, 07, 23), Value = 190, Type = TransactionType.Credit, Account = 2 },
    new Transaction { ID = 2, Date = new DateTime(2015, 07, 28), Value = 350, Type = TransactionType.Credit, Account = 2 },
    new Transaction { ID = 3, Date = new DateTime(2015, 08, 14), Value = 450, Type = TransactionType.Credit, Account = 2 },
    new Transaction { ID = 4, Date = new DateTime(2015, 08, 30), Value = 100, Type = TransactionType.Debit, Account = 2 },
    new Transaction { ID = 5, Date = new DateTime(2015, 09, 22), Value = 100, Type = TransactionType.Credit, Account = 2 },
};

原来的问题是这样回答的

decimal maxCredit = 1000;

针对特定帐户

int account = 1;
bool hasBonus = transactions
    .Where(t => t.Account == account)
    .GetCreditInfo().Any(info => info.Value >= maxCredit);

对于所有帐户

var bonusInfo = transactions.GroupBy(t => t.Account, (key, elements) => new
{ 
    Account = key,
    HasBonus = elements.GetCreditInfo().Any(info => info.Value >= maxCredit)
}).ToList();

其他

var maxCreditInfo = transactions.GroupBy(t => t.Account, (key, elements) => new
{
    Account = key,
    MaxCredit = elements.GetCreditInfo().Max(info => info.Value)
}).ToList();

var bonusTransactionInfo = transactions.GroupBy(t => t.Account, (key, elements) => new
{
    Account = key,
    BonusTransactions = elements.GetCreditInfo()
        .Where(info => info.Key.IsCredit && info.Value >= maxCredit).ToList()
}).ToList();

等等