累加算法结构

本文关键字:结构 算法 | 更新日期: 2023-09-27 18:29:31

我有一个表,可以在其中查找与帐户相关的交易。此表包含借方和贷方交易记录。

我需要帮助构建一个算法,计算账户在特定时刻是否从未达到定义的值(假设为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;
    }
}

累加算法结构

假设您有一个事务类

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

正如您所说,您可以从DB中获取它们,从而覆盖该部分。您获得了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;
}

编辑: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;
}

一个更改进的解决方案是,如果您可以在数据库中移动它,消除一次又一次地访问数据库的

有没有什么通用而简单的方法来进行这种计算?

对于此型号,没有。

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查询可以用于回答不同的问题,包括帖子中的原始问题。

例如,让我们取您的样本数据

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

等等。

下面是一个使用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为假)

  2. 将一笔交易"添加"到此状态的委托(在这里,我计算新余额,如果它>=1000,则设置HasBonus)

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

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

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

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

以良好的性能解决问题的真正关键在于如何读取事务数据,以及如何使其他组件可以访问它。

确保将事务作为Enumerable返回,并使用yield return ...从读取循环中返回单个事务。

当您使用Entity Framework时,只要您不执行ToList()ToArray()Count()或类似的东西,就不必担心这一点,这些东西在代码早期的某个地方具体化了事务集合。