2010-07-20 147 views
5

我有一個List<T1>項目和第二個項目List<T2>。這兩個列表按屬性A按字母順序排序。我知道List<T2>中的項目列表是List<T1>的子集,並且中不存在List<T1>中不存在的項目。遍歷2個列表

我需要迭代List<T1>並在每次匹配變量List<T2>時更改變量。什麼是最快和最好的方式來做到這一點?我假設我需要遍歷這兩個列表,但我知道做一個嵌套的foreach是沒有意義的。

+0

是相同類型的列表嗎? – SLaks 2010-07-20 17:12:51

+0

列表多久?如果我們在談論微小的數字,不要排除一些非常簡單的O(n^2)原油解決方案。 – 2010-07-20 17:31:42

+0

'從List1中的x連接y在x.P中的List2中等於y.P'? – Gabe 2010-07-20 17:50:37

回答

11

對於這種類型的東西,我更喜歡雙重循環。看下面的例子。

var super = new List<Contact>(); 
super.Add(new Contact() {Name = "John"}); 
super.Add(new Contact() {Name = "Larry"}); 
super.Add(new Contact() {Name = "Smith"}); 
super.Add(new Contact() {Name = "Corey"}); 

var sub = new List<Contact>(); 
sub.Add(new Contact() {Name = "Larry"}); 
sub.Add(new Contact() {Name = "Smith"}); 

var subCount = 0; 
for(int i=0; i<super.Count && subCount < sub.Count; i++) 
{ 
    if (super[i].Name == sub[subCount].Name) 
    { 
     Act(super[i], sub[subCount]); 
     subCount++; 
    } 
} 

其中Act(...)執行您正在尋找的任何操作。

循環每次增加超級列表,但只在您找到匹配時遞增子列表。

請注意,這隻適用於你的兩個假設。 1)列表都是排序的,2)第二個列表是第一個列表的子集。

+0

起初我以爲這是錯的。但是,「sub」是「super」的一個子集,這是一個比我更清潔的解決方案,它只是假設排序,因此必須處理跳過錯過的匹配。雖然這不處理具有相同屬性值的多個條目。 – jdmichal 2010-07-20 17:36:16

+0

對。這些假設對於這種方法很重要。 – EndangeredMassa 2010-07-20 18:49:25

+0

該方法將遍歷每個超級列表項目的每個子列表項目。這意味着它循環N * M次,其中N和M是超級列表和子列表的大小。它可以這樣工作,但我的方法只循環N次,其中N是超級列表的長度。 – EndangeredMassa 2010-07-20 19:42:52

5

如果名單是不是太大,您這樣做最簡單的方法是調用Contains

foreach(var item in list1) { 
    if (list2.Contains(item) { 
     //Do something 
    } 
} 

你可以使其更快通過使用自定義IComparer<T>調用BinarySearch,像這樣:

var hashset = new HashSet<YourClass>(list2); 
foreach(var item in list1) { 
    if (hashset.Contains(item) { 
     //Do something 
    } 
} 
class MyComparer : IComparer<YourClass> { 
    private MyComparer() { } 
    public static readonly MyComparer Instance = new MyComparer(); 

    public int CompareTo(YourClass a, YourClass b) { 
     //TODO: Handle nulls 
     return a.SomeProperty.CompareTo(b.SomeProperty); 
    } 
} 
foreach(var item in list1) { 
    if (list2.BinarySearch(item, MyComparer.Instance) >= 0) { 
     //Do something 
    } 
} 

.NET 3.5中,你可以通過使用HashSet<T>使其更快

如果您的列表非常大,您應該測量每個選項的性能並進行相應選擇。
否則,請選擇其中一個最簡單的選項。

1

如果它們都在唯一屬性上排序,則可以在迭代過程中使用它。這個想法是循環遍歷超集,然後基於排序後的唯一屬性推進子集迭代器,直到它匹配或者更大/更小(取決於排序順序)而不是超集。

對於升序排序屬性:

if (subsetList.Count > 0) 
{ 
    using(IEnumerator<T2> subset = subsetList.GetEnumerator()) 
    { 
     subset.MoveNext(); 
     T2 subitem = subsetList.Current; 
     foreach(T1 item in supersetList) 
     { 
      while (item.A > subitem.A && 
        subset.MoveNext()) 
      { 
       subitem = subset.Current; 
      } 

      if (item.A == subitem.A) 
      { 
       // Modify item here. 
      } 
     } 
    } 
} 

注意,這實際上並不依賴於supersetList是的subsetList一個超集。在假設成立的情況下,EndangeredMassa的解決方案更爲簡潔。

+0

這與我的回答相同,只是您不處理超集中有多個條目等於子集中的單個條目的情況。 – 2010-07-20 17:29:11

+0

這是處理。除非超集超出該項目,否則它不會迭代子項。因此,超集中相同值的多個條目不會推進子集迭代器。儘管我在while循環中做了比較。固定。 – jdmichal 2010-07-20 17:31:34

1

您的問題意味着您要避免每次都迭代第二個列表中的所有項目,這是在使用Contains()的最糟糕的天真解決方案中會發生的情況。由於這兩個列表都是排序的,並且list2list1的子集,因此您知道list1中的條目的索引將小於list2中的相應條目。考慮到這一點,您可以使用兩個統計員製作高效的O(n)解決方案:

Debug.Assert(list1.Count > 0); 
Debug.Assert(list1.Count >= list2.Count); 

var enum1 = list1.GetEnumerator(); 
var enum2 = list2.GetEnumerator(); 

enum1.MoveNext(); 
while (enum2.MoveNext()) 
{ 
    // Skip elements from list1 that aren't equal to the current entry in list2 
    while (!enum1.Current.Equals(enum2.Current)) 
     enum1.MoveNext(); 

    // Fire the OnEqual event for every entry in list1 that's equal to an entry 
    // in list2 
    do { 
     OnEqual(enum1.Current, enum2.Current); 
    } while (enum1.MoveNext() && enum1.Current.Equals(enum2.Current)); 
} 

enum1.Dispose(); 
enum2.Dispose(); 
+0

這就是我一直在尋找的! Thx,mate !;) – user1859587 2013-01-23 13:52:10