2012-03-05 27 views
15

從集合中刪除集合的最佳方法是什麼,但是仍然保留在單獨集合中刪除的項目?查找並從集合中刪除項目

我寫了一個擴展方法,但我認爲必須有更好的方法。這裏是我的功能:

public static List<T> FindAndRemove<T>(this List<T> lst, Predicate<T> match) 
{ 
    List<T> ret = lst.FindAll(match); 
    lst.RemoveAll(match); 
    return ret; 
} 

而且你會使用這樣的:

List<String> myList = new List<String>(); 
myList.Add("ABC"); 
myList.Add("DEF"); 
myList.Add("ABC"); 
List<String> removed = myList.FindAndRemove(x => x == "ABC"); 
// myList now contains 1 item (DEF) 
// removed now contains 2 items (ABC, ABC) 

我不是100%肯定發生的事情在FindAllRemoveAll方法在幕後,但我想更好的方法是以某種方式將項目從一個列表「轉移」到另一個列表。

+1

您的解決方案是最高效的。 – 2012-03-05 15:31:26

+2

你的實現對我來說看起來很好。複製在.Net中是一種廉價的操作,因此沒有任何「傳輸」的理由(除非您需要某種線程/異常安全性,否則對象一次不會超過一個集合) – adrianm 2012-03-05 15:43:46

+0

我同意。使用內置的LINQ是讓生活更輕鬆的目的..場景將是MS選擇的最佳解決方案。因爲你在C#中很好看,但VB會菊花鏈式查詢'return = lst.FindAll(match).RemoveAll(match)'以保持與閱讀代碼風格的一致性。 – ppumkin 2012-03-05 16:26:10

回答

1

我不認爲它是最有效率的 - 你在列表的每個元素上調用謂詞match兩次。

我會做這樣的:

var ret = new List<T>(); 
    var remaining = new List<T>(); 
    foreach (T t in lst) { 
     if (match(t)) 
     { 
      ret.Add(t); 
     } 
     else 
     { 
      remaining.Add(t); 
     } 
    } 
    lst.Clear(); 
    lst.AddRange(remaining); 
    return ret; 
+0

OMG So C++ days ..遍歷列表..不是.NET'ish。 – ppumkin 2012-03-05 16:22:04

+0

一次遍歷列表,並盡最大努力實現所需結果。什麼是不喜歡? – 2012-03-05 16:26:35

+1

@ppumkin:另一種方法是創建一個列表來保存找到的結果並在之後進行迭代,因爲您不能在foreach中對列表進行變異。 – Guvante 2012-03-05 16:31:51

0

根據您的集合的大小,你可能要實現它作爲一個HashSet,而不是列表。在足夠大的集合中(根據我的經驗,多大的「足夠」一定取決於集合中的內容),HashSets比查找列表中的項目要快得多,速度更快。

9

到目前爲止Op的答案是最好的建議和建議的解決方案。這裏是我的機器上的時間:

public static class Class1 
{ 
    // 21ms on my machine 
    public static List<T> FindAndRemove<T>(this List<T> lst, Predicate<T> match) 
    { 
     List<T> ret = lst.FindAll(match); 
     lst.RemoveAll(match); 
     return ret; 
    } 

    // 538ms on my machine 
    public static List<T> MimoAnswer<T>(this List<T> lst, Predicate<T> match) 
    { 
     var ret = new List<T>(); 
     int i = 0; 
     while (i < lst.Count) 
     { 
      T t = lst[i]; 
      if (!match(t)) 
      { 
       i++; 
      } 
      else 
      { 
       lst.RemoveAt(i); 
       ret.Add(t); 
      } 
     } 
     return ret; 
    } 

    // 40ms on my machine 
    public static IEnumerable<T> GuvanteSuggestion<T>(this IList<T> list, Func<T, bool> predicate) 
    { 
     var removals = new List<Action>(); 

     foreach (T item in list.Where(predicate)) 
     { 
      T copy = item; 
      yield return copy; 
      removals.Add(() => list.Remove(copy)); 
     } 

     // this hides the cost of processing though the work is still expensive 
     Task.Factory.StartNew(() => Parallel.ForEach(removals, remove => remove())); 
    } 
} 

[TestFixture] 
public class Tester : PerformanceTester 
{ 
    [Test] 
    public void Test() 
    { 
     List<int> ints = Enumerable.Range(1, 100000).ToList(); 
     IEnumerable<int> enumerable = ints.GuvanteSuggestion(i => i % 2 == 0); 
     Assert.That(enumerable.Count(), Is.EqualTo(50000)); 
    } 
} 
+0

感謝您提供的時間 – 2012-05-24 21:58:05

0

你應該試圖做的是將你的原始列表分成兩個新的列表。該實現應該適用於任何IEnumerable,而不僅僅是列表,並且應該假定源是不可變的。 看到這個職位分區: LINQ Partition List into Lists of 8 members。 我認爲MoreLinq已經涵蓋了它。