271

I have a class as follows:

public class Tag {
    public Int32 Id { get; set; }
    public String Name { get; set; }
}

And I have two lists of tag:

List<Tag> tags1;
List<Tag> tags2;

I used LINQ's select to get the Ids of each tags list. And then:

List<Int32> ids1 = new List<Int32> { 1, 2, 3, 4 };
List<Int32> ids2 = new List<Int32> { 1, 2, 3, 4 };
List<Int32> ids3 = new List<Int32> { 2, 1, 3, 4 };
List<Int32> ids4 = new List<Int32> { 1, 2, 3, 5 };
List<Int32> ids5 = new List<Int32> { 1, 1, 3, 4 };

ids1 should be equal to ids2 and ids3 ... Both have the same numbers.

ids1 should not be equal to ids4 and to ids5 ...

I tried the following:

var a = ints1.Equals(ints2);
var b = ints1.Equals(ints3);

But both give me false.

What is the fastest way to check if the lists of tags are equal?

UPDATE

I am looking for POSTS which TAGS are exactly the same as the TAGS in a BOOK.

IRepository repository = new Repository(new Context());

IList<Tags> tags = new List<Tag> { new Tag { Id = 1 }, new Tag { Id = 2 } };

Book book = new Book { Tags = new List<Tag> { new Tag { Id = 1 }, new Tag { Id = 2 } } };

var posts = repository
  .Include<Post>(x => x.Tags)
  .Where(x => new HashSet<Int32>(tags.Select(y => y.Id)).SetEquals(book.Tags.Select(y => y.Id)))
  .ToList();

I am using Entity Framework and I get the error:

An exception of type 'System.NotSupportedException' occurred in mscorlib.dll but was not handled in user code

Additional information: LINQ to Entities does not recognize the method 'Boolean SetEquals(System.Collections.Generic.IEnumerable`1[System.Int32])' method, and this method cannot be translated into a store expression.

How do I solve this?

Community
  • 1
  • 1
Miguel Moura
  • 36,732
  • 85
  • 259
  • 481
  • What do you mean by not equality exacly, do you mean all elements should be different or just they shouldn't contains same elements,at least there should be one different? – Selman Genç Mar 04 '14 at 14:00
  • Your sequence `ids5` contains duplicates. Is that intentional? – Sergey Kalinichenko Mar 04 '14 at 14:06
  • @Selman22 I mean that the two lists should contain exactly the same elements ... The order does not matter – Miguel Moura Mar 04 '14 at 14:20
  • @dasblinkenlight Yes, it does not make since since in this case IDs are unique because they are primary keys. – Miguel Moura Mar 04 '14 at 14:20
  • You may want to post the updated question separately, because after the edit the question will have an entirely different solution from anything that has been posted so far. Add `[EF]` tag, and make sure that the title of the new question says "comparing lists inside EF's Where clause" or something similar. – Sergey Kalinichenko Mar 04 '14 at 14:26
  • The answers here are fairly different than the marked duplicate, because this specifically deals with lists of value types, as opposed to the other question that is a list of reference types. The answers to the linked question are significantly more complicated due to dealing with the reference types. Nominating to reopen – AaronLS Jul 17 '14 at 17:53
  • I agree with @AaronLS except that to me the main problem of the dup target is that it's trying to deal with lists with the same elements but not necessarily in the same order. This question is simpler and the answer should reflect that. Hence I've re-opened this question. – Kirk Woll Apr 06 '22 at 17:14

3 Answers3

513

Use SequenceEqual to check for sequence equality because Equals method checks for reference equality.

var a = ints1.SequenceEqual(ints2);

Or if you don't care about elements order use Enumerable.All method:

var a = ints1.All(ints2.Contains);

The second version also requires another check for Count because it would return true even if ints2 contains more elements than ints1. So the more correct version would be something like this:

var a = ints1.All(ints2.Contains) && ints1.Count == ints2.Count;

In order to check inequality just reverse the result of All method:

var a = !ints1.All(ints2.Contains)
Selman Genç
  • 100,147
  • 13
  • 119
  • 184
  • Maybe using Except and Interect would be a better option? I am not sure if your solution is fast when integrated in a Linq to Entities query since I I need to select all the IDS multiple times ... Or maybe I am wrong? – Miguel Moura Mar 04 '14 at 14:24
  • I'm not sure.You need to try both and look at the generated sql.BTW did you try this solution ? did you get `NotSupportedException` or did it converted succesfully? – Selman Genç Mar 04 '14 at 14:40
  • 18
    Slow and doesn't handle duplicates. `[1, 1, 2] != [1, 2, 2]` – CodesInChaos Mar 04 '14 at 15:42
  • 1
    @CodesInChaos according to OP's comment in the question duplicates doesn't matter – Selman Genç Mar 04 '14 at 15:55
  • 23
    Note: You might be used to using `.All` with a lambda like `.All(i=> ints2.Contains(i))`, but since list.Contains() matches the function signature of taking a `int` and returning a `bool`, then he is passing the function name directly as a predicate. Essentially the same as `ints1.All(i=> ints2.Contains(i))`. Just wanted to point this out in case others like me were initially confused. – AaronLS Jul 17 '14 at 18:01
  • 1
    Almost committed a bug since I implemented your second example not reading further and seeing a Count check was needed (third example). :) – Wollan Oct 26 '14 at 16:10
  • 45
    I would revert the order of the `var a = ints1.All(ints2.Contains) && ints1.Count == ints2.Count;` to `var a = ints1.Count == ints2.Count && ints1.All(ints2.Contains);`. A simple count comparison is likely much faster than the `.All` call. If counts are not equal it would return faster. – Spiralis Aug 24 '15 at 04:21
  • 12
    I will downvote this answer because this does not apply for all cases, List A {a,a} and list B contains {b,a} now ListB.All(listA.contains) and LIstA.All(listB.contains) will give different results, since both have same count, we will get true in one of them even though both are different, it wont work if a list has multiple entries – – Pratyush Dhanuka Jul 19 '18 at 07:17
  • `var a = ints1.All(ints2.Contains) && ints1.Count == ints2.Count;` Let's hope that `ints1` isn't `{1,1,2}` when `ints2` is `{1,2,3}`. Since this answer will say they are equivalent. – mjwills Oct 29 '18 at 07:00
  • Why am I getting an error about ints2.Contains? – Rob Vermeulen Apr 02 '19 at 06:34
  • @AaronLS, You mentioned above that you don't need to create a complete lambda with a parameter list passed in for the 'ints1.All(ints2.Contains)'. How far does concept go in the .Net ecosystem? Is it just for any predicate within a LINQ query, or is it seen elsewhere in .Net? – Evan Sevy Aug 22 '19 at 18:42
  • All(Contains) is O(N^2), you can do better by sorting. – Justin Meiners Sep 11 '19 at 01:00
  • Couldn't you combine the GetHashCode() of all elements for both lists, then compare the two resulting hash codes? Is there a method that already exists that does this? – Douglas Gaskell Nov 02 '19 at 00:17
  • You are assuming that .SequenceEqual does not check for reference equality but microsoft docs says different. https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.sequenceequal?view=netcore-3.1 If you want to compare the actual data of the objects in the sequences instead of just comparing their references, you have to implement the IEqualityComparer generic interface in your class. – Flori Bruci Oct 23 '20 at 14:30
  • I think this one does work with duplicates: `var listsAreEqual = list1.Count == list2.Count && list1.All(a => list2.Count(i => i.Name == a.Name) == list1.Count(i => i.Name == a.Name));` It will iterate over all elements and check if both list contains the equal amount of them. Of course this could still be improved performance wise but that is not the clue of my comment. – Jordy Feb 23 '22 at 13:56
151

List<T> equality does not check them element-by-element. You can use LINQ's SequenceEqual method for that:

var a = ints1.SequenceEqual(ints2);

To ignore order, use SetEquals:

var a = new HashSet<int>(ints1).SetEquals(ints2);

This should work, because you are comparing sequences of IDs, which do not contain duplicates. If it does, and you need to take duplicates into account, the way to do it in linear time is to compose a hash-based dictionary of counts, add one for each element of the first sequence, subtract one for each element of the second sequence, and check if the resultant counts are all zeros:

var counts = ints1
    .GroupBy(v => v)
    .ToDictionary(g => g.Key, g => g.Count());
var ok = true;
foreach (var n in ints2) {
    int c;
    if (counts.TryGetValue(n, out c)) {
        counts[n] = c-1;
    } else {
        ok = false;
        break;
    }
}
var res = ok && counts.Values.All(c => c == 0);

Finally, if you are fine with an O(N*LogN) solution, you can sort the two sequences, and compare them for equality using SequenceEqual.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
29
Enumerable.SequenceEqual(FirstList.OrderBy(fElement => fElement), 
                         SecondList.OrderBy(sElement => sElement))
Pankaj
  • 2,618
  • 3
  • 25
  • 47
  • 9
    The name of your lambda parameter is weird. They're no list, they're an element. I'd either go with `id` in the OP's context, or `element` in a generic context. – CodesInChaos Mar 04 '14 at 15:40
  • This guy clearly copied this answer from elsewhere since judging by the parameter names he doesn't even know what it does.. – Mark Silver Feb 01 '17 at 11:56
  • 1
    I think it covers exactly the issue at hand. It compares two lists, no matter the sequence. – Rob Vermeulen Apr 02 '19 at 06:36
  • You can even add `.Distinct()` before the `OrderBy`s if you want to compare and ignore duplicates. This is definitely not the most efficient method of comparing sets. HashSet.SetEquals ignores duplicates and order. Enumerable.SequenceEquals considers duplicates and preserves order. – Triynko Mar 09 '22 at 20:01