18

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

abhilash
  • 5,605
  • 3
  • 36
  • 59

8 Answers8

28

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
  • 687,336
  • 108
  • 737
  • 1,005
  • 1
    I think this will only work if the number of elements in the list is even. – tvanfosson Mar 07 '09 at 13:26
  • I think i just needs to start at 1 not 0 then this code will work – AnthonyWJones Mar 07 '09 at 13:35
  • @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 Mar 07 '09 at 13:43
  • +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 Mar 07 '09 at 13:50
  • & yes.. this is most efficient answer.. posted so far. – abhilash Mar 07 '09 at 13:58
  • 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 Mar 07 '09 at 14:04
  • This DOES NOT WORK for list as a list does not have operator[] – Martin York Mar 07 '09 at 15:55
  • Removing an element from a list is guaranteed to be O(1). So the order elements are removed in is immaterial. – Martin York Mar 07 '09 at 15:56
  • Yes, it does have an indexer: http://msdn.microsoft.com/en-us/library/0ebtbkkc.aspx No, the RemoveAt method is an O(n) operation: http://msdn.microsoft.com/en-us/library/5cw9x18z.aspx – Guffa Mar 07 '09 at 16:07
  • 1
    @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 Mar 07 '09 at 21:23
9

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!

Derek Beattie
  • 9,429
  • 4
  • 30
  • 44
flq
  • 22,247
  • 8
  • 55
  • 77
  • 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 Mar 07 '09 at 13:47
  • 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 Mar 07 '09 at 13:56
  • Repping this - simple, and an iterator is often exactly what you need. – rjh Mar 07 '09 at 13:57
  • @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 Mar 07 '09 at 14:04
  • The OP specifically asked for a solution without a place holder list. – Guffa Mar 07 '09 at 14:06
  • 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. – andleer Mar 07 '09 at 15:54
5

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();
Martin Jonáš
  • 2,309
  • 15
  • 12
2

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
  • 733,204
  • 149
  • 1,241
  • 1,454
  • 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 Mar 07 '09 at 13:24
  • @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 Mar 07 '09 at 13:33
  • 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 Mar 07 '09 at 13:38
  • @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 Mar 07 '09 at 13:41
  • @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 Mar 07 '09 at 13:46
  • @Guffa: I agree this would not be at all effecient on the basis that internaly List does actually shift stuff about. Yours would be more efficient. However does List shift stuff about as we assume, or might it not have a more pragmatic internal implementation? – AnthonyWJones Mar 07 '09 at 13:46
  • Sorry. My mistake -- I see now that you're depending on the reordering. – tvanfosson Mar 07 '09 at 13:50
  • I haven't had my coffee, yet. :-( – tvanfosson Mar 07 '09 at 13:51
  • @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 Mar 07 '09 at 13:52
  • I checked the implementation using .NET Reflector. The RemoveAt method uses Array.Copy to move the data in the internal array. – Guffa Mar 07 '09 at 13:57
  • @Guffa, All of the List methods have the potential to use this method. – JaredPar Mar 07 '09 at 14:02
  • It gets worse. I just started up the coffeemaker without water. No performing brain surgery for me today! – tvanfosson Mar 07 '09 at 14:10
  • @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 Mar 07 '09 at 14:12
2

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
  • 45,157
  • 15
  • 111
  • 168
1
for (int i=myList.length-1; i >= 0; i--)
  if (i % 2 == 0)
    myList.Remove(myList[i]);
tsilb
  • 7,977
  • 13
  • 71
  • 98
1

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
  • 1,974
  • 14
  • 21
0

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
  • 257,169
  • 86
  • 333
  • 562