tags:

views:

353

answers:

6

What is the preferable way for transferring some items (not all) from one list to another.

What I am doing is the following:

var selected = from item in items
               where item.something > 10
               select item;

otherList.AddRange(selected);

items.RemoveAll(item => selected.Contains(item));

In the interest of having the fastest/best code there is, is there a better way?

+1  A: 

I suggest:

var selected = items.Where(item => item.Something > 10).ToList();
items = items.Except(selected).ToList();
otherList.AddRange(selected);
Mehrdad Afshari
+4  A: 

That is quite bad performance wise - it actually enumerates a query n times (for n items in items). It would be better if you built (for example) a HashSet<T> of the items to manipulate.

To give a simple example just with int values:

    var items = new List<int> { 1, 2, 3, 4, 5, 6 };
    var otherList = new List<int>();
    var selected = new HashSet<int>(items.Where(
        item => item > 3));
    otherList.AddRange(selected);
    items.RemoveAll(selected.Contains);
Marc Gravell
I'm not sure if I correctly see how the query will enumerate n times... Is it because of the use of the "selected" query in both the AddRange and RemoveAll? At worst, I thought that the enumeration would go only twice...
Stecy
"selected" is an IEnumerable<> query - *not* a container. "selected.Contains" enumerates this query each time it is invoked.
Marc Gravell
Oh! I see now. You're right.
Stecy
Could I use another list instead of a HashSet? I fail to see why it would be better...
Stecy
Stecy: Lookup is O(n) in a list while it can be O(1) in a HashSet. Yet, I believe from the readability perspective, an Except method which uses a HashSet internally works better.
Mehrdad Afshari
A: 

I would look at using the ForEach method on List to handle this. Also, you probably want to standardize on using either the query syntax or method syntax, not both.

Correl
+1  A: 

I'd try @Mehrdad's answer, and maybe test it against this one too...

var selected = items.Where(item => item.Something > 10).ToList();
selected.ForEach(item => items.Remove(item));
otherList.AddRange(selected);
Scott Ivey
Simple! accepted answer.
Stecy
+2  A: 

RemoveAll goes trough each item and enumerates all values of your selected list every time. This will take longer than it should...

What I'd do is put the condition directly in the RemoveAll parameter:

items.RemoveAll(item => item.something > 10);

If you do this and don't change the rest of your code there would be code duplication, which is not good. I'd do the following to avoid it:

Func<ItemType, bool> selectedCondition = (item => item.something > 10);

otherList.AddRange(items.Where(selectedCondition));

items.RemoveAll(selectedCondition);

I'm programming in VB usually so there could be some syntax errors...

Meta-Knight
Indeed. However, I'd still want to have only one place with the condition...
Stecy
Then check my edit ;)
Meta-Knight
+1. This is the best method (unless the Where condition is much more complex than checking element equality). Just make sure you are not changing the collection between the method calls. You might lose elements.
Mehrdad Afshari
btw, you can't use `var` with a lambda (even if you fix the broken syntax).
Marc Gravell
I think it's fixed now. Was there any other "broken" syntax besides my missing semicolon?
Meta-Knight
A: 

How about a partition:

    int[] items = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
    var partition = items.ToLookup(x => x > 5);
    var part1 = partition[true];
    var part2 = partition[false];
Ray