Way to Check If Two Collections Contain the Same Elements, Independent of Order

Simple way to find if two different lists contain exactly the same elements?

If you care about order, then just use the equals method:

list1.equals(list2)

From the javadoc:

Compares the specified object with
this list for equality. Returns true
if and only if the specified object is
also a list, both lists have the same
size, and all corresponding pairs of
elements in the two lists are equal.
(Two elements e1 and e2 are equal if
(e1==null ? e2==null :
e1.equals(e2)).) In other words, two
lists are defined to be equal if they
contain the same elements in the same
order. This definition ensures that
the equals method works properly
across different implementations of
the List interface.

If you want to check independent of order, you could copy all of the elements to Sets and use equals on the resulting Sets:

public static <T> boolean listEqualsIgnoreOrder(List<T> list1, List<T> list2) {
return new HashSet<>(list1).equals(new HashSet<>(list2));
}

A limitation of this approach is that it not only ignores order, but also frequency of duplicate elements. For example, if list1 was ["A", "B", "A"] and list2 was ["A", "B", "B"] the Set approach would consider them to be equal.

If you need to be insensitive to order but sensitive to the frequency of duplicates you can either:

  • sort both lists (or copies) before comparing them, as done in this answer to another question
  • or copy all elements to a Multiset

Is there a way to check if two Collections contain the same elements, independent of order?

Apache commons-collections has CollectionUtils#isEqualCollection:

Returns true if the given Collections contain exactly the same elements with exactly the same cardinality.

That is, if the cardinality of e in a is equal to the cardinality of e in b, for each element e in a or b.

Which is, I think, exactly what you're after.

Determine if 2 lists have the same elements, regardless of order?

You can simply check whether the multisets with the elements of x and y are equal:

import collections
collections.Counter(x) == collections.Counter(y)

This requires the elements to be hashable; runtime will be in O(n), where n is the size of the lists.

If the elements are also unique, you can also convert to sets (same asymptotic runtime, may be a little bit faster in practice):

set(x) == set(y)

If the elements are not hashable, but sortable, another alternative (runtime in O(n log n)) is

sorted(x) == sorted(y)

If the elements are neither hashable nor sortable you can use the following helper function. Note that it will be quite slow (O(n²)) and should generally not be used outside of the esoteric case of unhashable and unsortable elements.

def equal_ignore_order(a, b):
""" Use only when elements are neither hashable nor sortable! """
unmatched = list(b)
for element in a:
try:
unmatched.remove(element)
except ValueError:
return False
return not unmatched

Check if two list have the same items

That's what sets (e.g., HashSet<T>) are for. Sets have no defined order, and SetEquals verifies whether the set and another collection contain the same elements.

var set = new HashSet<int>(list1);
var equals = set.SetEquals(list2);

Comparing two collections for equality irrespective of the order of items in them

It turns out Microsoft already has this covered in its testing framework: CollectionAssert.AreEquivalent

Remarks

Two collections are equivalent if they
have the same elements in the same
quantity, but in any order. Elements
are equal if their values are equal,
not if they refer to the same object.

Using reflector, I modified the code behind AreEquivalent() to create a corresponding equality comparer. It is more complete than existing answers, since it takes nulls into account, implements IEqualityComparer and has some efficiency and edge case checks. plus, it's Microsoft :)

public class MultiSetComparer<T> : IEqualityComparer<IEnumerable<T>>
{
private readonly IEqualityComparer<T> m_comparer;
public MultiSetComparer(IEqualityComparer<T> comparer = null)
{
m_comparer = comparer ?? EqualityComparer<T>.Default;
}

public bool Equals(IEnumerable<T> first, IEnumerable<T> second)
{
if (first == null)
return second == null;

if (second == null)
return false;

if (ReferenceEquals(first, second))
return true;

if (first is ICollection<T> firstCollection && second is ICollection<T> secondCollection)
{
if (firstCollection.Count != secondCollection.Count)
return false;

if (firstCollection.Count == 0)
return true;
}

return !HaveMismatchedElement(first, second);
}

private bool HaveMismatchedElement(IEnumerable<T> first, IEnumerable<T> second)
{
int firstNullCount;
int secondNullCount;

var firstElementCounts = GetElementCounts(first, out firstNullCount);
var secondElementCounts = GetElementCounts(second, out secondNullCount);

if (firstNullCount != secondNullCount || firstElementCounts.Count != secondElementCounts.Count)
return true;

foreach (var kvp in firstElementCounts)
{
var firstElementCount = kvp.Value;
int secondElementCount;
secondElementCounts.TryGetValue(kvp.Key, out secondElementCount);

if (firstElementCount != secondElementCount)
return true;
}

return false;
}

private Dictionary<T, int> GetElementCounts(IEnumerable<T> enumerable, out int nullCount)
{
var dictionary = new Dictionary<T, int>(m_comparer);
nullCount = 0;

foreach (T element in enumerable)
{
if (element == null)
{
nullCount++;
}
else
{
int num;
dictionary.TryGetValue(element, out num);
num++;
dictionary[element] = num;
}
}

return dictionary;
}

public int GetHashCode(IEnumerable<T> enumerable)
{
if (enumerable == null) throw new
ArgumentNullException(nameof(enumerable));

int hash = 17;

foreach (T val in enumerable)
hash ^= (val == null ? 42 : m_comparer.GetHashCode(val));

return hash;
}
}

Sample usage:

var set = new HashSet<IEnumerable<int>>(new[] {new[]{1,2,3}}, new MultiSetComparer<int>());
Console.WriteLine(set.Contains(new [] {3,2,1})); //true
Console.WriteLine(set.Contains(new [] {1, 2, 3, 3})); //false

Or if you just want to compare two collections directly:

var comp = new MultiSetComparer<string>();
Console.WriteLine(comp.Equals(new[] {"a","b","c"}, new[] {"a","c","b"})); //true
Console.WriteLine(comp.Equals(new[] {"a","b","c"}, new[] {"a","b"})); //false

Finally, you can use your an equality comparer of your choice:

var strcomp = new MultiSetComparer<string>(StringComparer.OrdinalIgnoreCase);
Console.WriteLine(strcomp.Equals(new[] {"a", "b"}, new []{"B", "A"})); //true

How can I perform an order-independent equality check on lists?

If you're using Eclipse Collections, you can convert both Lists to Bags and just use equals() between the Bags. The contract of Bag.equals() is that two Bags are equal if they have the same number of each element, but order doesn't factor in. There's a performance benefit too. toBag() and Bag.equals() are each O(n), so this method is faster than sorting the Lists.

Assert.assertEquals(
Lists.mutable.with(1, 2, 3, 1).toBag(),
Lists.mutable.with(3, 2, 1, 1).toBag());

Note: I am a committer for Eclipse Collections.

Compare two List<T> objects for equality, ignoring order

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.SequenceEqual(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);
}

Edit 2:

To handle any data type as key (for example nullable types as Frank Tzanabetis pointed out), you can make a version that takes a comparer for the dictionary:

public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2, IEqualityComparer<T> comparer) {
var cnt = new Dictionary<T, int>(comparer);
...

Check if two arrays have the same contents (in any order)

This doesn't require conversion to set:

a.sort == b.sort


Related Topics



Leave a reply



Submit