2016-08-24 90 views
4

我有以下列表:改善目前的LINQ的時間複雜查詢

RakeSnapshots, ProductMovements

目的是既處理,並得到符合條件的元素的數量,具體如下:

  • 考慮RakeSnapshotsStatusCode == "Dumping"

  • 考慮ProductMovementStatus == "InProgress"

  • 抓取所有的元素都列出,滿足條件RakeSnapshots.RakeCode等於ProductMovements.ProductCode

以下是我目前的選擇的count

//代碼1:

var resultCount = ProductMovements.Where(x => RakeSnapshots 
               .Where(r => r.StatusCode == "Dumping") 
               .Any(y => y.RakeCode == x.ProductCode && 
                  x.Status == "InProgress")) 
               .Count(); 

//代碼2:

var productMovementsInprogress = ProductMovements.Where(x => x.Status == "InProgress"); 

var rakeSnapShotsDumping = RakeSnapshots.Where(r => r.StatusCode == "Dumping"); 

var resultCount = productMovementsInprogress.Zip(rakeSnapShotsDumping,(x,y) => (y.RakeCode == x.ProductCode) ? true : false) 
              .Where(x => x).Count(); 

挑戰既是代碼O(n^2)複雜,有沒有改善它的方式,如果數據是非常大的

+0

是什麼'RakeSnapshots - RakeCode等於ProductMovements - ProductCode'是什麼意思? –

+0

我們可以將'RakeCode'與'ProductCode'進行比較,如問題中的示例所示。編輯,有複製和粘貼錯誤 –

+0

第二個變體不等同於第一個。第一個返回預期的結果嗎? –

回答

3

您可以使用inner join做到這一點:

var dumpingRakeSnapshots  = rakeSnapshots.Where(r => r.StatusCode == "Dumping"); 
var inProgressProductMovements = productMovements.Where(p => p.Status == "InProgress"); 

var matches = 
    from r in dumpingRakeSnapshots 
    join p in inProgressProductMovements on r.RakeCode equals p.ProductCode 
    select r; 

int count = matches.Count(); // Here's the answer. 

注意(伊萬Stoev指出的)這僅適用於如果RakeCode是RakeSnapshots主鍵。

如果不是,您將不得不使用grouped join

下面是你應該在這種情況下使用,但請注意,這是完全一樣伊萬的答案(僅在Linq查詢形式)LINQ查詢語法版本:

var matches = 
    from r in dumpingRakeSnapshots 
    join p in inProgressProductMovements on r.RakeCode equals p.ProductCode into gj 
    select gj; 

爲了完整起見,這裏是一個演示了不同的結果可編譯控制檯應用程序,你會得到,如果RakeCodeProductCode不是主鍵:

using System; 
using System.Collections.Generic; 
using System.Linq; 

namespace ConsoleApp1 
{ 
    class RakeSnapshot 
    { 
     public string StatusCode; 
     public string RakeCode; 
    } 

    class ProductMovement 
    { 
     public string Status; 
     public string ProductCode; 
    } 

    sealed class Program 
    { 
     void run() 
     { 
      var rakeSnapshots = new List<RakeSnapshot> 
      { 
       new RakeSnapshot {StatusCode = "Dumping", RakeCode = "1"}, 
       new RakeSnapshot {StatusCode = "Dumping", RakeCode = "1"}, 
       new RakeSnapshot {StatusCode = "Dumping", RakeCode = "2"} 
      }; 

      var productMovements = new List<ProductMovement> 
      { 
       new ProductMovement {Status = "InProgress", ProductCode = "1"}, 
       new ProductMovement {Status = "InProgress", ProductCode = "2"}, 
       new ProductMovement {Status = "InProgress", ProductCode = "2"} 
      }; 

      var dumpingRakeSnapshots  = rakeSnapshots.Where(r => r.StatusCode == "Dumping"); 
      var inProgressProductMovements = productMovements.Where(p => p.Status == "InProgress"); 

      // Inner join. 

      var matches1 = 
       from r in dumpingRakeSnapshots 
       join p in inProgressProductMovements on r.RakeCode equals p.ProductCode 
       select r; 

      Console.WriteLine(matches1.Count()); 

      // Grouped join. 

      var matches2 = 
       from r in dumpingRakeSnapshots 
       join p in inProgressProductMovements on r.RakeCode equals p.ProductCode into gj 
       select gj; 

      Console.WriteLine(matches2.Count()); 

      // OP's code. 

      var resultCount = 
       productMovements 
       .Count(x => rakeSnapshots 
       .Where(r => r.StatusCode == "Dumping") 
       .Any(y => y.RakeCode == x.ProductCode && x.Status == "InProgress")); 

      Console.WriteLine(resultCount); 
     } 

     static void Main(string[] args) 
     { 
      new Program().run(); 
     } 
    } 
} 
+1

這真的取決於OP代碼#1是否產生了期望的結果。既然它使用'Any',那麼你應該用'where'加入'',把它變成基本查詢'GroupJoin'的語法。 –

+0

雖然如果連接字段在各自的集合中是唯一的,那麼簡單的'內部連接'就像你的答案一樣:) –

+0

@IvanStoev這是一個很好的觀點。我將更新我的答案,以包含組聯接的查詢語法版本(但該部分將相當於您在此時的答案)。 –

3

聽起來Group Join這樣會傷害它(以及Join)是最有效的LINQ方式關聯兩組:

var resultCount = ProductMovements.Where(p => p.Status == "InProgress") 
    .GroupJoin(RakeSnapshots.Where(r => r.StatusCode == "Dumping"), 
     p => p.ProductCode, r => r.RakeCode, (p, match) => match) 
    .Count(match => match.Any()); 

以上的時間複雜度是O(N + M)。

1

常,用O(N^2),你會創建一箇中間的'搜索'數據結構來加速查找。類似於O(1)訪問的散列表,或者O(log N)訪問的排序列表。

從技術上講,你有兩個不同的列表,所以實際的訂單是O(P.R),其中P是產品移動的數量,R是耙子快照的數量。

就你而言,這是你的原始代碼;

var resultCount = ProductMovements 
    .Where(x => RakeSnapshots 
     .Where(r => r.StatusCode == "Dumping") 
     .Any(y => y.RakeCode == x.ProductCode && 
        x.Status == "InProgress")) 
     .Count(); 

爲O(PR),因爲每一個P,內where子句通過每一個R.循環我想看看創造一個Dictionary<T>HashSet<T>,然後改變你的代碼,類似

var rakeSnapshotSummary = ... magic happens here ...; 
var resultCount = ProductMovements 
    .Where(x => rakeSnapshotSummary[x.ProductCode] == true) 
    .Count(); 

以這種方式,創建快照是O(R),查找到所述數據結構是O(1),和創建的結果是O(P),對於更健康O(P + R)。我的事情就是這樣。

所以我對你的指數例行程序的建議會是這樣的;

var rakeSnapshotSummary = new HashSet<string>(RakeSnapshots 
    .Where(r => r.StatusCode == "Dumping") 
    .Select(r => r.RakeCode)); 

這產生了HashSet<string>這將有用於的耙代碼測試所有腦幹O(1)時間複雜度。然後最終行看起來像

var resultCount = ProductMovements 
    .Where(x => x.Status == "InProgress" && rakeSnapshotSummary.Contains(x.ProductCode)) 
    .Count(); 

所以,總體來說,O(P + R)或,大致,O(2N)=> O(N)。

+1

謝謝,我確實考慮過類似的設計,但我需要一個純粹的linq解決方案,這是運行時執行框架所需的,並且沒有中間數據結構的範圍 –