Is There a Built-In Method to Compare Collections

Is there a built-in method to compare collections?

Enumerable.SequenceEqual

Determines whether two sequences are equal by comparing their elements by using a specified IEqualityComparer(T).

You can't directly compare the list & the dictionary, but you could compare the list of values from the Dictionary with the list

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

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

Python: how to compare custom class member to builtin type

You can add __lt__ and __gt__ methods to the Foo class (less than and greater than) that evaluate Foo objects as being less than or greater than integers, this is used by sort when a key function is not provided

class Foo():

def __lt__(self, other):
if isinstance(other, int):
return self._id < other
elif isinstance(other, self.__class__):
return self._id < other._id

def __gt__(self, other):
if isinstance(other, int):
return self._id > other
elif isinstance(other, self.__class__):
return self._id > other._id

Method to Compare objects

Your problem is that your method is generic. When you call it on the sub-collections, the type you are using for the comparison is System.Object, which of course has no interesting properties to compare.

Change your method declaration and the preamble of the method:

public static void CompareObjects(object obj1, object obj2)
{
if (obj1.GetType() != obj2.GetType())
{
return;
}

foreach (var property in obj1.GetType().GetProperties())
{
...

Note that with this approach, you also do not need the dynamic and ChangeType() code.

Finally, you may want to consider changing your inequality comparison so that instead of using the != operator it uses the Equals() method:

        if (!obj1Prop.Equals(obj2Prop))
{
Console.WriteLine($"Object 1 {property.Name}:{obj1Prop}{Environment.NewLine}Object 2 {property.Name}:{obj2Prop}{Environment.NewLine}");
}

Even when the method was generic, that would be a good idea; while one is supposed to implement the equality and inequality operators if one overrides Equals(), that doesn't always happen. When the method is non-generic, of course you won't even get operator overloads for the specific types (at least not without a lot of extra work).



(I guess you could instead of the above use the same dynamic/ChangeType() approach you use for the non-enumerable properties, using e.g. list1.Current.GetType() as the type instead of property.PropertyType; that could allow the method to remain generic. But there's so much extra overhead to dynamic and ultimately it will all come back down to executing the same code, I just don't see the point in doing it that way.)



Related Topics



Leave a reply



Submit