累加算法结构
本文关键字:结构 算法 | 更新日期: 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()的一般形式有三个参数:
开始状态(在这种情况下,账户余额最初为零,HasBonus为假)
将一笔交易"添加"到此状态的委托(在这里,我计算新余额,如果它>=1000,则设置HasBonus)
代表采取最终状态并从中获得我们想要的答案(这里只是通过获得HasBonus标志)
通过调整第二个代理中的逻辑,您可以准确地控制在何种条件下授予奖金。
您的代码看起来可以很好地解决您的问题。然而,在你的循环中,你应该在达到平衡阈值后立即中断,以避免多余的计算:
if ( counter >= 1000M ) {
hasBonus = true;
// Stop iterating through transactions.
break;
}
以良好的性能解决问题的真正关键在于如何读取事务数据,以及如何使其他组件可以访问它。
确保将事务作为Enumerable
返回,并使用yield return ...
从读取循环中返回单个事务。
当您使用Entity Framework时,只要您不执行ToList()
、ToArray()
、Count()
或类似的东西,就不必担心这一点,这些东西在代码早期的某个地方具体化了事务集合。