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?


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);
        }
    }
4

3 回答 3

5

如果您调用list.Remove它将删除第一个实例;但是,您可以使用list.RemoveAtand list.RemoveRange。例如:

list.RemoveRange(5,2);

或更好:首先不要添加它们

例如,如果您从一个序列中添加,您可以在添加时使用DistinctHashSet<T>查找唯一项目。


事后,您可以使用:

    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);
        }
    }

删除除第一个重复项之外的所有重复项。

或者也许更有效:

    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);
        }
    }
于 2013-05-17T06:48:29.957 回答
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

于 2013-05-17T06:58:26.217 回答
-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.

于 2013-05-17T06:55:22.443 回答