0

I have

 var a = new object();
 var b = new object();
 var c = new object();
 var d = new object();
 var e = new object();
 var list = new List<object> {a, b, c, d, e, a, d};

Then I need to remove last two objects (a and d), because they already in the list.
How I can do it without creating new List?

7
  • Is it possible to prevent those objects from being appended to the list in the first place? Commented May 17, 2013 at 6:49
  • 2
    "without creating new List" - what is this constraint really about? Creating a new list isn't an expensive operation in the grand scheme of things. Commented May 17, 2013 at 6:49
  • There's imho no way with LINQ , because it temporarily creates new lists for every operation to achieve the task. And again there's the question: Why? Commented May 17, 2013 at 6:51
  • 1
    @Damien_The_Unbeliever: Why is it suprising that someone doesn't want to create a new list which requires more memory, hence increases the risk of an OutOfMemoryException? Commented May 17, 2013 at 7:14
  • 1
    @TimSchmelter - it's not surprising, as such, more that it's an unusual requirement. Unusual requirements can frequently come about due to someone misunderstanding some aspect of .NET/C#. So it helps to ask the question (and helps if the person is actually willing to explain their reasoning) because you may be able to provide more actual help to the OP if you understand where they're coming from. Commented May 17, 2013 at 8:09

3 Answers 3

5

If you call list.Remove it will remove the first instance; you can, however, use list.RemoveAt and list.RemoveRange. For example:

list.RemoveRange(5,2);

or better: don't add them in the first place.

For example, if you are adding from a sequence you can use Distinct or HashSet<T> to find unique items when adding.


After the fact, you could use:

    var dups = from item in list
               group item by item into grp
               where grp.Count() > 1
               select grp.Key;

    foreach (var val in dups)
    {
        int first = list.IndexOf(val), last;
        while ((last = list.LastIndexOf(val)) != first)
        {
            list.RemoveAt(last);
        }
    }

To remove all but the first instance of duplicates.

Or perhaps more efficiently:

    for (int i = 0; i < list.Count; i++ )
    {
        var val = list[i];
        int first = list.IndexOf(val), last;
        while ((last = list.LastIndexOf(val)) != first)
        {
            list.RemoveAt(last);
        }
    }
Sign up to request clarification or add additional context in comments.

3 Comments

But how he would get "2" without any other type of IEnumerable?
@Tigran which is why (see code above) I'm using LastIndexOf - avoids that pain
Thanks for answer. last part - what I'm looking for.
3

You could use this backwards for-loop and Enumerable.Contains + List.RemoveAt. This will not create a new list:

var list = new List<object> { "a", "b", "c", "d", "e", "a", "d" };
for (int i = list.Count - 1; i >= 0; i--)
{
   var obj = list[i];
   if(list.Take(i).Contains(obj))
       list.RemoveAt(i);
}

So this loop gets one object via indexer, takes all objects before it's index and compares it with each other. Note that Take doesn't create a collection due to it's deferred execution, understood it as a loop, it'll end as soon as Contains returns true. If it's already available it will be removed from the list. So the early bird gets the worm.

However, if you want to support custom types you need to override Equals and GetHashCode in your class otherwise Contains will just compare the references.

Demo

1 Comment

already nice answer. Sorry, but I can't accept more then one. Only +1
-1

How I can do it without creating new List?

You requirement is somewhat surprising (or at least you didn't explain why it is important that the list has to be modified in place), but here is a solution that favors speed of memory usage:

var set = new HashSet<object>();
var indicesToRemove = new List<int>();
for (var i = 0; i < list.Count; ++i) {
  var item = list[i];
  if (!set.Contains(item))
    set.Add(item);
  else
    indicesToRemove.Add(i);
}
var itemsRemovedSoFar = 0;
foreach (var i in indicesToRemove) {
  list.RemoveAt(i - itemsRemovedSoFar);
  itemsRemovedSoFar += 1;
}

Compare this to a solution where a new list is created:

var list = list.Distinct().ToList();

I certainly prefer the second solution but it doesn't satisfy you requirement.

4 Comments

You requirement is somewhat surprising Why is it suprising that someone don't want to create a new list which requires more memory, hence increases the risk of an OutOfMemoryException?
If the underlying problem is that the source list is very big and should not be duplicated then my solution isn't useful because if the list doesn't have any duplicates the backing storage of the HashSet will consume the same amount of storage as the source list. However, even though some of the other proposed solutions doesn't consume more memory they are very inefficient in terms of CPU required because the repeatedly search the source list for duplicates. This solution avoids this inefficiency by using more memory.
I just wanted to point out that there's a good reason to avoid duplicating collections even if the backing collections are temporary. It might be unimportant if the program needs a few seconds more(e.g. if it's a windows-service) but it's probably important that it runs without an exception. Also, if the loop approach is inefficient against the hashset it would mean that the collection is really large. Then the memory issue is even more important.
@TimSchmelter: Completely agree. My solution is not good if the source is very large and memory is a concern. However, why the list was to be modified in place was not specified in the question. Another possible reason could be that some third party API was involved making it impossible to replace the list.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.