views:

665

answers:

6

I am looking for a better 'pattern' for working with a list of elements which each need processed and then depending on the outcome are removed from the list.

You can't use .Remove(element) inside a foreach (var element in X)... you also can't use for (int i = 0; i < elements.Count(); i++) and .RemoveAt(i).

Previously I have done crazy things like this (summary = building a generic structure around the 'element' to hold a true or false value which determines whether or not it should be removed from the list):

public List<String> AttemptToProcessList(List<string> list) {

    var listTracked = new List<KeyValuePair<string, bool>>();

    list.ForEach(item => listTracked.Add(new KeyValuePair<string, bool>(item, false)));

    for (int i = 0; i < listTracked.Count; i++) {

        var result = ProcessListItem(listTracked[i].Key);

        if (result) {
            listTracked[i] = new KeyValuePair<string, bool>(listTracked[i].Key, true);
        }
     }

     foreach (var item in listTracked.Where( listItem => listItem.Value )) {
         list.Remove(item.Key);
     }

     return list;
}
A: 

You can use for instead of foreach. Foreach relies on enumeration and you really cannot modify your collection while you're enumerating it.

Sergey
But using 'for (int i = 0; i < elements.Count(); i++)' and 'elements.RemoveAt(i)' changes the indexing of the list. So when when the next element is processed (after i++ in the for statement executes and the last element has been removed) you end up with the next element in the list not being processes... e.g. i=5 and on the last iteration element[4] was removed so what was element[5] before element[4] was removed now becomes element[4] and since i=5 you start processing element[5] missing the newly repositioned element[4].
CuriousCoder
A: 

I would reassign safePendingList from a LINQ query that filtered out the elements you didn't want to keep.

Martin Liversage
+8  A: 

A simple and straightforward solution:

Use a standard for-loop running backwards on your collection and RemoveAt(i) to remove elements.

Jan
+1: backwards traversal is the key :)
Ahmad Mageed
+15  A: 

Iterate your list in reverse with a for loop:

for (int i = safePendingList.Count - 1; i >= 0; i--)
{
    // some code
    // safePendingList.RemoveAt(i);
}

Example:

var list = new List<int>(Enumerable.Range(1, 10));
for (int i = list.Count - 1; i >= 0; i--)
{
    if (list[i] > 5)
     list.RemoveAt(i);
}
list.ForEach(i => Console.WriteLine(i));

Alternately, you can use the RemoveAll method with a predicate to test against:

safePendingList.RemoveAll(item => item.Value == someValue);

Here's a simplified example to demonstrate:

var list = new List<int>(Enumerable.Range(1, 10));
Console.WriteLine("Before:");
list.ForEach(i => Console.WriteLine(i));
list.RemoveAll(i => i > 5);
Console.WriteLine("After:");
list.ForEach(i => Console.WriteLine(i));
Ahmad Mageed
+2  A: 

Using the ToArray() on a generic list allows you to do a Remove(item) on your generic List:

        List<String> strings = new List<string>() { "a", "b", "c", "d" };
        foreach (string s in strings.ToArray())
        {
            if (s == "b")
                strings.Remove(s);
        }
Etienne Brouillard
This isn't wrong but I have to point out that this bypasses the need to create a 2nd "storage" list of the items you want removed at the expense of copying the entire list to an array. A 2nd list of hand-picked elements will probably have less items.
Ahmad Mageed
+4  A: 

Select the elements you do want rather than trying to remove the elements you don't want. This is so much easier (and generally more efficient too) than removing elements.

var newSequence = (from el in list
                   where el.Something || el.AnotherThing < 0
                   select el);

I wanted to post this as a comment in response to the comment left by Michael Dillon below, but it's too long and probably useful to have in my answer anyway:

Personally, I'd never remove items one-by-one, if you do need removal, then call RemoveAll which takes a predicate and only rearranges the internal array once, whereas Remove does an Array.Copy operation for every element you remove. RemoveAll is vastly more efficient.

And when you're backwards iterating over a list, you already have the index of the element you want to remove, so it would be far more efficient to call RemoveAt, because Remove first does a traversal of the list to find the index of the element you're trying to remove, but you already know that index.

So all in all, I don't see any reason to ever call Remove in a for-loop. And ideally, if it is at all possible, use the above code to stream elements from the list as needed so no second data structure has to be created at all.

JulianR
Either this, or add a pointer to the unwanted elements into a second list, then after your loop ends, iterate the removal list and use that to remove the elements.
Michael Dillon