views:

165

answers:

5

Yet another list-comparing question.

I have: List<MyType> list1; List<MyType> list2;

I need to check that they both have the same elements, regardless of their position within the list. Each MyType object may appear multiple times on a list. Is there a built-in function that checks this? What if I guarantee that each element appears only once in a list?

EDIT: Guys thanks for the answers but I forgot to add something, the number of occurrences of each element should be the same on both lists.

+1  A: 

Thinking this should do what you want:

list1.All(item => list2.Contains(item)) &&
list2.All(item => list1.Contains(item));

if you want it to be distinct, you could change it to:

list1.All(item => list2.Contains(item)) &&
list1.Distinct().Count() == list1.Count &&
list1.Count == list2.Count
Brian Genisio
list2 could contain extra items.
recursive
@recursive: edited to account for that. Thanks.
Brian Genisio
You can still get a false positive. Consider {1, 2, 2} and {1, 1, 2}, they contain the same items, has the same count, but still are not equal.
Guffa
@Guffa: Good point. I think I got it now, with the `list1.Distinct()` addition. If all the items are the same, and list 1 is distinct, and they both have the same lengths, then list 2 must also be distinct. Now, {1,2} and {2,1} are considered the same, but {1,2,2} and {1,1,2} are not.
Brian Genisio
While technically correct, the behavior of `Contains()` may result in O(N<sup>2</sup>) performance. The set operations (Except, Intersect, Union) perform much better if the number of items large.
LBushkin
@Brian Genisio: Now get a false negative on {1, 2, 2} and {1, 2, 2}...
Guffa
@Guffa: You should. The lists are not distinct. The second answer assumes that the lists are distinct and contain the same values.
Brian Genisio
+2  A: 

If you don't care about the number of occurrences, I would approach it like this. Using hash sets will give you better performance than simple iteration.

var set1 HashSet<MyType>(list1);
var set2 HashSet<MyType>(list2);
return set1.SetEquals(set2);

This will require that you have overridden .GetHashCode() on MyType if necessary.

recursive
Do I just need to implement the GetHashCode on MyType then?
Bruno Teixeira
@Bruno: Yes. Good point.
recursive
@recursive: This will not take into account duplicates, as the OP indicates he needs in his edit. This will, however, work if duplicates can be ignored.
LBushkin
The `HashSet<T>` also needs you to implement the `Equals` method. The items are compared against each other, it's not just the hash codes that are compared.
Guffa
@Guffa: All the other approaches here require `Equals` also.
recursive
@recursive: Yes, but the answer mentiones only `GetHashCode`, as if `Equals` would not be needed.
Guffa
+2  A: 

This is a slightly difficult problem, which I think reduces to: "Test if two lists are permutations of each other."

I believe the solutions provided by others only indicate whether the 2 lists contain the same unique elements. This is a necessary but insufficient test, for example {1, 1, 2, 3} is not a permutation of {3, 3, 1, 2} although their counts are equal and they contain the same distinct elements.

I believe this should work though, although it's not the most efficient:

static bool ArePermutations<T>(IList<T> list1, IList<T> list2)
{
   if(list1.Count != list2.Count)
         return false;

   var l1 = list1.ToLookup(t => t);
   var l2 = list2.ToLookup(t => t);

   return l1.Count == l2.Count 
       && l1.All(group => l2.Contains(group.Key) && l2[group.Key].Count() == group.Count()); 
}
Ani
+2  A: 

As written, this question is ambigous. The statement:

... they both have the same elements, regardless of their position within the list. Each MyType object may appear multiple times on a list.

does not indicate whether you want to ensure that the two lists have the same set of objects or the same distinct set.

If you want to ensure to collections have exactly the same set of members regardless of order, you can use:

// lists should have same count of items, and set difference must be empty
var areEquivalent = (list1.Count == list2.Count) && !list1.Except(list2).Any();

If you want to ensure two collections have the same distinct set of members (where duplicates in either are ignored), you can use:

// check that [(A-B) Union (B-A)] is empty
var areEquivalent = !list1.Except(list2).Union( list2.Except(list1) ).Any();

Using the set operations (Intersect, Union, Except) is more efficient than using methods like Contains. In my opinion, it also better expresses the expectations of your query.

EDIT: Now that you've clarified your question, I can say that you want to use the first form - since duplicates matter. Here's a simple example to demonstrate that you get the result you want:

var a = new[] {1, 2, 3, 4, 4, 3, 1, 1, 2};
var b = new[] { 4, 3, 2, 3, 1, 1, 1, 4, 2 };

// result below should be true, since the two sets are equivalent...
var areEquivalent = (a.Count() == b.Count()) && !a.Except(b).Any(); 
LBushkin
+4  A: 

If you want them to be really equal (i.e. the same items and the same number of each item), I think that the simplest solution is to sort before comparing:

Enumerable.SequenceEquals(list1.OrderBy(t => t), list2.OrderBy(t => t))

Edit:

Here is a solution that performs a bit better (about ten times faster), and only requires IEquatable, not IComparable:

public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2) {
  var cnt = new Dictionary<T, int>();
  foreach (T s in list1) {
    if (cnt.ContainsKey(s)) {
      cnt[s]++;
    } else {
      cnt.Add(s, 1);
    }
  }
  foreach (T s in list2) {
    if (cnt.ContainsKey(s)) {
      cnt[s]--;
    } else {
      return false;
    }
  }
  return cnt.Values.All(c => c == 0);
}
Guffa
@Guffa: This is a good answer, I believe it is correct, and it is shorter than mine. My only suggestion is to use `SequenceEqual` as an extension method. I should also point out that this requires that `T` be `IComparable`, whereas the `ToLookup` version only requires a correct `GetHashCode` and `Equals` implementation.
Ani
I think this is the simplest approach. I'm using: list1.sort(); list2.sort(); return Enumerable.SequenceEquals(list1, list2); Now I'm having problems in comparing elements. Can you guys give me some pointers? In Java I only have to implement equals or hash, here it doesnt seem to work. Thanks
Bruno Teixeira
This approach only works if `MyType` is comparable (or a custom comparer is supplied) and a total ordering of items is possible. Otherwise you can get false negatives. It's also doing more work than necessary - specifically, if the lists have a different number of items they can't possibly be equal.
LBushkin
@Bruno Teixeira: Write a correct `Equals` and `GetHashCode` implementation; ideally implement `IEquatable<T>`. Then either implement `IComparable<T>` (ideally) and use this technique or use a `ToLookup` or some other associative-array technique.
Ani