4

I'd like to use Remove() method on list of lists, but it's not working for me.
Simple example should say everything:

List<List<int>> list = new List<List<int>>();
list.Add(new List<int> { 0, 1, 2 });
list.Add(new List<int> { 1, 2 });
list.Add(new List<int> { 4 });
list.Add(new List<int> { 0, 1, });

list.Remove(new List<int> { 1, 2 });

If I use RemoveAt(1) it works fine but Remove() not.
It is obviously the same reason that this code returns false:

List<int> l1 = new List<int>();
List<int> l2 = new List<int>();
l1.Add(1);
l2.Add(1);

bool b1 = l1 == l2; // returns False
bool b2 = l1.Equals(l2); // returns False too

So it seems to me that I cannot simply compare two lists or even arrays. I can use loops instead of Remove(), but there must be easier way.

Thanks in advance.

cozzy
  • 147
  • 1
  • 3
  • 8
  • Instead of creating a List of type List, have you considered creating your own class? – Jamie Keeling Apr 09 '11 at 20:12
  • 1
    Not an answer, but a reason for why it's not working. [List.Remove()](http://msdn.microsoft.com/en-us/library/cd666k3e.aspx) determines equality by using the default equality comparer [EqualityComparer.Default](http://msdn.microsoft.com/en-us/library/ms224763.aspx). Just a little extra information that might help other people help you. – Only Bolivian Here Apr 09 '11 at 20:15
  • @Jamie: It would be helpful if you told him why creating his own class would help him in this case. :) – Only Bolivian Here Apr 09 '11 at 20:16
  • @Sergio Apologies, @cozzy Are you using the List object specifically for some of it's features or is it something that's convenient? – Jamie Keeling Apr 09 '11 at 20:18
  • @Jamie Keeling: I've choosen Lists instead of arrays because I don't need to set the size of array at the beginning. I haven't thought about anything else and I have no clue how to work with my new class or type. – cozzy Apr 09 '11 at 22:02

6 Answers6

11

The problem is that List<T> doesn't override Equals and GetHashCode, which is what List<T> will use when trying to find an item. (In fact, it will use the default equality comparer, which means it'll use the IEquatable<T> implementation if the object implements it, and fall back to object.Equals/GetHashCode if necessary). Equals will return false as you're trying to remove a different object, and the default implementation is to just compare references.

Basically you'd have write a method to compare two lists for equality, and use that to find the index of the entry you want to remove. Then you'd remove by index (using RemoveAt). EDIT: As noted, Enumerable.SequenceEqual can be used to compare lists. This isn't as efficient as it might be, due to not initially checking whether the counts are equal when they can be easily computed. Also, if you only need to compare List<int> values, you can avoid the virtual method call to an equality comparer.

Another alternative is to avoid using a List<List<int>> in the first place - use a List<SomeCustomType> where SomeCustomType includes a List<int>. You can then implement IEquatable<T> in that type. Note that this may well also allow you to encapsulate appropriate logic in the custom type too. I often find that by the type you've got "nested" collection types, a custom type encapsulates the meaning of the inner collection more effectively.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Highlights my comment well in regards to using a custom type :) – Jamie Keeling Apr 09 '11 at 20:30
  • @Jamie: Yup, we think alike :) – Jon Skeet Apr 09 '11 at 20:41
  • @Jon: I made some coffee and sat down and worked on an example for this and I can't believe I'm just learning about it. It's such a great tool. :D – Only Bolivian Here Apr 09 '11 at 22:04
  • Look at jCoder's answer. Do you think that your idea better? What's best for performance? – cozzy Apr 09 '11 at 22:26
  • @cozzy: SequenceEqual in LINQ to Objects isn't as smart as it could be - it doesn't check for the count of each comparison first. You could do that yourself. It's worth noting that jCoder's answer isn't equivalent to calling Remove - because it removes *all* matches instead of just the first. Is that okay for you? How large are your lists? How much are you sure you care about performance? Personally I'd still strongly consider writing my own type to encapsulate the lists, as presumably they have some meaning beyond just "lists of ints" - it would be good to express that. – Jon Skeet Apr 10 '11 at 07:36
  • @cozzy: However, jCoder's answer is certainly pretty readable, so if it does what you want, it's a good solution. – Jon Skeet Apr 10 '11 at 07:36
  • @Jon: The original posting provided no infos if the sequence to remove will occur more than once so I thought it would be okay to remove all of them. And I'd go for an encapsulating object or at least an own comparison method too, if there's more meaning behind the inner List. Sure, the solution is readable, but can be optimized depending on its context. – jCoder Apr 10 '11 at 10:12
  • @jCoder: Well, the OP was trying to use `List.Remove`, which always removes the first occurrence - so that's what I assumed was required. But yes, the more we know about real requirements and semantic meaning (and expected size), the more we could help. – Jon Skeet Apr 10 '11 at 10:16
6

First approach:

List<int> listToRemove = new List<int> { 1, 2 };
list.RemoveAll(innerList => innerList.Except(listToRemove).Count() == 0);

This also removes the List { 2, 1 }

Second approach (preferred):

List<int> listToRemove = new List<int> { 1, 2 };
list.RemoveAll(innerList => innerList.SequenceEqual(listToRemove));

This removes all lists that contain the same sequence as the provided list.

jCoder
  • 2,289
  • 23
  • 22
  • +1 for answering the actual question directly. Not that all the advice in the other answers isn't relevant, but people are suggesting that 'it just can't be done' the way OP has tried. – Kimberly Apr 09 '11 at 21:14
  • It seems to do what I want and it's the easiest way. So I will probably use it, Thanks. – cozzy Apr 09 '11 at 22:09
4

List equality is reference equality. It won't remove the list unless it has the same reference as a list in the outer list. You could create a new type that implements equality as set equality rather than reference equality (or you do care about order as well?). Then you could make lists of this type instead.

tvanfosson
  • 524,688
  • 99
  • 697
  • 795
2

This simply won't work because you're tying to remove a brand new list (the new keyword kind of dictates such), not one of the ones you just put in there. For example, the following code create two different lists, inasmuch as they are not the same list, however much they look the same:

var list0 = new List<int> { 1, 2 };

var list1 = new List<int> { 1, 2 };

However, the following creates one single list, but two references to the same list:

var list0 = new List<int> { 1, 2 };

var list1 = list0;

Therefore, you ought to keep a reference to the lists you put in there should you want to act upon them with Remove in the future, such that:

var list0 = new List<int> { 1, 2 };

listOfLists.Remove(list0);
Grant Thomas
  • 44,454
  • 10
  • 85
  • 129
1

They are different objects. Try this:

  List<int> MyList =  new List<int> { 1, 2 };   

  List<List<int>> list = new List<List<int>>();
  list.Add(new List<int> { 0, 1, 2 });
  list.Add(MyList);
  list.Add(new List<int> { 4 });
  list.Add(new List<int> { 0, 1, });

  list.Remove(MyList);
FerranB
  • 35,683
  • 18
  • 66
  • 85
0

You need to specify the reference to the list you want to remove:

list.Remove(list[1]);

which, really, is the same as

list.RemoveAt(1);
Charlie Salts
  • 13,109
  • 7
  • 49
  • 78