views:

465

answers:

8

What is the most efficient way to remove alternate (odd indexed or even indexed) elements in an List<T> without using a place holder list variable?

Also it would be appreciated if you could mention the cost with each of your answer.

I'm looking for an efficient way to do this

Thanks in advance

+2  A: 

I'm not sure what you mean by alternate but if you mean "every other item" the following code will work. It will start by removing the 2nd element, then the 4th, and so on

List<T> list = GetTheList();
int i = 1;
while ( i < list.Count ) {
  list.RemoveAt(i);
  i++;
}
JaredPar
When you remove an element at the beginning, all the rest will shift down one. This certainly won't eliminate alternate elements and may result in an exception depending on whether the guard is recalculated at every step, i.e., list.Count will be changing.
tvanfosson
@tvanfonsson: Code looks good to me?
AnthonyWJones
@Anthony -- when you remove the element at position 2, the element at position 3 shifts to 2, 4 shifts to 3, etc., so the next element that gets removed was actually at position 5 in the original list, not position 4.
tvanfosson
It does work, but it's very inefficient. To process a list of 1000 ints, you will be moving close to one MB of data...
Guffa
@tvanfosson: the code appears to be relying on the behaviour you describe as part of the solution. Note i increments only by 1 iteration, in combination with the position shifting you describe it will only remove every other item, as expected.
AnthonyWJones
@tvanfonsson, it definitely eliminates the alternate elements in the list. I didn't say I would remove the element at 4 I said the 4th element which is at index 3.
JaredPar
@Guffa: I agree this would not be at all effecient on the basis that internaly List<T> does actually shift stuff about. Yours would be more efficient. However does List<T> shift stuff about as we assume, or might it not have a more pragmatic internal implementation?
AnthonyWJones
Sorry. My mistake -- I see now that you're depending on the reordering.
tvanfosson
I haven't had my coffee, yet. :-(
tvanfosson
@tvanfosson, you should be down voted for even thinking about posting on SO without coffee. :). I've learned the hard way not to do that. Just about to start cup #2
JaredPar
I checked the implementation using .NET Reflector. The RemoveAt method uses Array.Copy to move the data in the internal array.
Guffa
@Guffa, All of the List<T> methods have the potential to use this method.
JaredPar
It gets worse. I just started up the coffeemaker without water. No performing brain surgery for me today!
tvanfosson
@tvanfosson I did the exact same thing last week. I even started it twice thinking I must have just forgot to push the button the first time. The smell of plastic clued me in.
JaredPar
+21  A: 

If you call RemoveAt for every item you remove, you will be moving a lot of data. The most efficient is to move the items together that you want to keep, then remove the unused items at the end:

int pos = 0;
for (int i = 0; i < values.Count; i += 2, pos++) {
 values[pos] = values[i];
}
values.RemoveRange(pos, values.Count - pos);

Edit:
This method will process a list of a million ints in 15 ms. Using RemoveAt it will take over three minutes...

Edit2:
You could actually start with pos=1 and i=2 (or 3), as the first item doesn't have to be copied to itself. This makes the code a bit less obvious though.

Guffa
I think this will only work if the number of elements in the list is even.
tvanfosson
I think i just needs to start at 1 not 0 then this code will work
AnthonyWJones
@tvanfosson: No, I have verified that the code works both for even and odd number of elements.@AnthonyWJones: If you want to remove the even indices instead of the odd, you would start at 1 instead of 0.
Guffa
+1 fun solution. What's slightly disturbing about this (and my) solution is that it will not change the size of the underlying array no matter how many times it's called and hence shrunk.
JaredPar
AB Kolan
Ah. I see. Copy the elements that you want to save and if there is an extra one at the end that you haven't iterated over, it must be one that you want to remove. Nice.
tvanfosson
This DOES NOT WORK for list<T> as a list does not have operator[]
Martin York
Removing an element from a list is guaranteed to be O(1). So the order elements are removed in is immaterial.
Martin York
Yes, it does have an indexer: http://msdn.microsoft.com/en-us/library/0ebtbkkc.aspxNo, the RemoveAt method is an O(n) operation: http://msdn.microsoft.com/en-us/library/5cw9x18z.aspx
Guffa
@Martin, it's unforntunately not O(1) as removal causes a shift in the elements in the array. The shift can be very costly: http://blogs.msdn.com/jaredpar/archive/2008/04/07/binaryinsert-part2.aspx
JaredPar
+5  A: 

Just for consideration of a solution that creates a new list, with a list old you could do this:

var newList = old.Where((_, i) => i%2 != 0).ToList();

or, obviously

var newList = l.Where((_, i) => i%2 == 0).ToList();

depending which alternation you choose.

EDIT

The answer is quite a bit quicker. If you read something else here, it's because I measured on a weekend and weekend's brain is funny. :( The closure solution is about 40% quicker while the answer is app. 2 orders of magnitude faster. I suppose it will really depend how big your list becomes!

flq
I think you put some F# code in there :)
JaredPar
Not sure if you say that ironically? It's definitely C# code, I tried it before I posted it here. Frankly, I am slightly surprised this isn't getting any rep, considering it's the simplest answer of all...
flq
This doesn't answer any of the OP's requirements. He wants to remove elements from the list, which this code doesn't do. He wants performance, which this code also doesn't provide.
Hosam Aly
Repping this - simple, and an iterator is often exactly what you need.
rjh
@Frank, I *thought* _ was an illegal identifier in C# because C# required a minimum of a single alpha character. Learn something new every day.
JaredPar
The OP specifically asked for a solution without a place holder list.
Guffa
This solution is really ideal. a slightly better variation on it might be list = list.Where((_, i) => i%2 != 0).ToList();The ref to the old list and all of the non-included items gets GCed based on all of the optimization in the GC. Hard to do better than that.
Andrew Robinson
+2  A: 

And another option, similar to Frank's one, but with usage of closures. And it is quicker than Frank's version.

bool isEven = true;            
var newList = list.Where(x => isEven = !isEven).ToList();
Matajon
+1  A: 
for (int i=myList.length-1; i >= 0; i--)
  if (i % 2 == 0)
    myList.Remove(myList[i]);
tsilb
A: 

I would use the standard pattern generally used for STL containers. Do a remove followed by an erase.

This way you will not confuse people that are used to seeing this pattern.

template<typename T>
struct RemoveEven
{
    RemoveEven():count(0)   {}
    bool operator()(T const&)
    {
        bool    result  =  count%2 == 0;
        count++;
        return result;
    }
    private:
        std::size_t count;
};
int main()
{
    std::list<int>  a;
    a.erase(std::remove_if(a.begin(),a.end(),RemoveEven<int>()),a.end());

}
Martin York
+1  A: 

Obviously usage dependent, but you could have a wrapper IList that multiplies the index you give it by 2, and reports the length of the list to be 1/2 (details elided). This is O(1).

wowest
Clever! But fragile.
Alex Reynolds
+2  A: 

The way to Nirvana is paved with deferred execution. Or something.

    public static IEnumerable<T> AlternateItems<T>(this IEnumerable<T> source)
    {
        while (source.Any())
        {
            yield return source.First();

            source = source.Skip(1);

            if (source.Any()) source = source.Skip(1);                
        }
    }

This works for all sequences, not just IList<>. The cost of iteration is deferred until iteration, which may be a big win if, in the end, you don't need to touch all the elements in the list.

In my simple tests, the performance when you iterate over the whole list is not very good, so be sure to profile your real situation.

Jay Bazuzi