2012-03-05 82 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已经涵盖了它。