2016-01-07 167 views
1

我有一張表,我可以找到與帳戶相關的交易。 此表包含借記和貸記交易。累積算法結構

我需要結構化算法來計算帳戶在特定時間內是否從未達到過定義值(假設爲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下降的平衡借記交易。所以我不應該考慮這個帳戶。

是否有任何一般和簡單的方法來做這個計算?

謝謝!

編輯:根據意見,這是什麼I'have至今:

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

可以使用SQL輕鬆地做到這一點。你試過什麼了? – mikeb

+0

我試圖通過代碼而不是sql。但是在我看來,代碼變得有點混亂。我讀取所有與linq-to-entities的交易,然後迭代所有交易以決定是否應積累。 – Lorenzo

+0

我不明白你的約束。爲什麼沒有考慮第二個帳戶?是否也有時間限制?你也應該簡化你的例子。您不需要六個示例交易來獲得您的觀點。 –

回答

1

是否有任何一般和簡單的方法來做這個計算?

有了這個模型,沒有。

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

+0

很棒的擴展! – Lorenzo

2

比方說你有一個交易類

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

更改進的解決辦法是,如果你能在數據庫移至此,消除了趟DB一遍又一遍

+0

首先感謝您的答案。我不認爲這會按我的需要工作。也許我在這個問題上沒有清楚。借方交易應重置金額,用戶實際上可以進行借方交易,並在此之後使許多信用達到該值。這可以簡單地說,在特定時刻,帳戶餘額大於或等於1000. – Lorenzo

+0

好的。如果賬戶餘額是900,並且用戶進行了150的借記交易,那麼餘額將是750?如果在此之後用戶進行了300的信用交易,餘額將是1050,這意味着它超過了1000並且帳戶是好的?或借記後,餘額應重置爲0並重新計算下一筆交易? –

+0

第一。請看看我的問題編輯。我發佈了我的實際解決方案(與您的解決方案大致相同)。我在想的唯一問題是隨着交易數量的增加,這個算法可能需要一段時間。我正在尋找一個更加可愛的解決方案。 – Lorenzo

1

下面是使用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(); 

通過最初由帳戶分組進行的交易,那麼,我們每個帳戶擁有它有足夠的信息,以找出是否HasBonus應該是真實交易的IEnumerable。

骨料()的廣義形式有三個參數:

  1. 的起始狀態(在這種情況下,帳戶餘額最初爲零和HasBonus爲假)

  2. 委託「加「一個交易到這個狀態(這裏我計算新的餘額,並設置HasBonus,如果> = 1000)

  3. 一個代表採取最終狀態並從中得到我們想要的答案(這裏只需通過獲取HasBonus標誌)

通過調整第二個委託中的邏輯,您可以精確控制您在哪些條件下獎勵獎金。

+0

感謝您的回答。我從來沒有通過linq擴展使用這個組。即使我懷疑這會比我的實現更好,我仍需要對它的性能進行比較。 – Lorenzo

1

你的代碼看起來很好解決你的問題。然而,你的循環裏面,你應該儘快你打巴蘭斯門檻斷裂,爲了避免不必要的計算:

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

的真正關鍵性能良好的解決你的問題在於你怎麼看這樣你交易數據以及您如何使其他組件可訪問。

確保您以Enumerable的形式返回交易,並使用yield return ...從您的讀取循環中返回單個交易。

當您使用實體框架,你不必,只要不用擔心這個,你不執行ToList()ToArray()Count()或類似的東西,在你的代碼的某個地方更早物化交易集合。

+1

與OP提供的代碼相比,這種實現有什麼好處(假設他在'hasBonus = true;'行後面加上'break')?因爲我看到的是 - 效率較低(涉及委託和關閉),可讀性較差。 –

+0

@IvanStoev,這是真的。我將刪除代碼。其中最重要的事實上是我談論列舉交易的最後一段。 –

+0

感謝您的回答。感謝關於休息的建議,我忘了它! – Lorenzo