What is the best way to remove a set from a collection, but still keep the items that were removed in a separate collection?
I have written an extension method that
Depending upon the size of your collection, you might want to implement it as a HashSet rather than a List. In sufficiently large collections (how large is "sufficient" has been somewhat dependent on what is in the collection, in my experience), HashSets can be much, much faster at finding items within themselves than Lists.
Op's answer is the best out of the proposed and suggested solutions so far. Here are timings on my machine:
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));
}
}
What you should be trying to do is partition your original list into two new lists. The implementation should work on any IEnumerable, not just lists, and should assume that the source is immutable. See this post on partitioning: LINQ Partition List into Lists of 8 members. I think MoreLinq has it covered already.
I don't agree that it is the most efficient - you are calling the predicate match
twice on each element of the list.
I'd do it like this:
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;