Compare Two List≪T≫ Objects For Equality, Ignoring Order

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);
...

How can I compare two lists of objects with ingorance of order?

If you don't have any duplicates, and you just want to see whether both lists contain the same set of IDs, then this is a set operation and the easiest solution uses a HashSet<int>:

bool same = list1.Select(x => x.Id).ToHashSet().SetEquals(list2.Select(x => x.Id));

You can optimize by checking the lengths of your two lists first: if they're different lengths, they're obviously different:

bool same = list1.Count == list2.Count &&
list1.Select(x => x.Id).ToHashSet().SetEquals(list2.Select(x => x.Id));

If you want to get the objects which are different between the two lists, you can use HashSet<T>.SymmetricExceptWith. The problem here is that we now need to be comparing Person objects directly, rather than taking their IDs first, because need those Person objects out of the other side.

Therefore, we'll need to write our own IEqualityComparer:

public class PersonIdComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y) => x.Id == y.Id;
public int GetHashCode(Person x) => x.Id.GetHashCode();
}

var set = list1.ToHashSet(new PersonIdComparer());
set.SymmetricExceptWith(list2);
// set now contains the differences

Comparing 2 lists of objects ignoring order

Because class ClientDto doesn't implement IEquatable<ClientDto> or IEquatable, instances are compared using reference equality.

Thus, the instances in the list will fail to compare - even though they contain the same data - because their references are different.

To fix this, just implement IEquatable<ClientDto> so that CollectionAssert.AreEquivalent() can compare the objects correctly:

public class ClientDto: IEquatable<ClientDto>
{
public string FullName { get; set; }

public decimal RentShare { get; set; }

public bool Equals(ClientDto? other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;

return FullName == other.FullName && RentShare == other.RentShare;
}

public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj))
return false;
if (ReferenceEquals(this, obj))
return true;
if (obj.GetType() != this.GetType())
return false;
return Equals((ClientDto) obj);
}

public override int GetHashCode()
{
unchecked
{
return (FullName.GetHashCode() * 397) ^ RentShare.GetHashCode();
}
}

public static bool operator ==(ClientDto? left, ClientDto? right)
{
return Equals(left, right);
}

public static bool operator !=(ClientDto? left, ClientDto? right)
{
return !Equals(left, right);
}
}

(Implementation courtesy of Resharper.)

Note that for recent versions of .Net Core and C#, you can use a record instead of a class, because a record implements IEquatable for you (along with a bunch of other things):

public record ClientDto
{
public string FullName { get; set; }
public decimal RentShare { get; set; }
}

Comparing a collection of reference type objects for equality ignoring order of items in collection

@Neil answer is correct, except that it won't work with reference types (string are an exception since they are immutable).

Item is a class so it is a reference type.

Except uses the default equality comparer to compare the elements. Since Item is a class, it will be compared by reference, which is not the desired solution. So we need to bypass the default comparison, with a custom equality comparer. An overload of Except exists for that purpose.

You will need to create a type that implements IEqualityComparer<Item> and pass an instance of that type to Except.

See: Except overload documentation and IEqualityComparer documentation

Here is an example that you can run in Linqpad. It uses both Except overloads. One return false, the other true:

void Main()
{
var basket1 = new Basket()
{
items = new Item[]
{
new Item() { name = "bread", price = 1.5 },
new Item() { name = "butter", price = 2 }
}
};

var basket2 = new Basket()
{
items = new Item[]
{
new Item() { name = "butter", price = 2 },
new Item() { name = "bread", price = 1.5 }
}
};

var isIdenticalByReference = (!(basket1.items.Except(basket2.items).Any())); // false
isIdenticalByReference.Dump();

var isIdenticalWithCustomEqualityComparer = (!(basket1.items.Except(basket2.items, new ItemEqualityComparer()).Any())); // true
isIdenticalWithCustomEqualityComparer.Dump();
}

// You can define other methods, fields, classes and namespaces here

public class Item
{
public string name { get; set; }
public double price { get; set; }

public int GetHashCode(object obj)
{
return (name?.GetHashCode() ?? 0) ^ price.GetHashCode();
}
}

public class ItemEqualityComparer : IEqualityComparer<Item>
{
public bool Equals(Item I1, Item I2)
{
if (I2 == null && I1 == null)
return true;
else if (I1 == null || I2 == null)
return false;
else return I1.name == I2.name && I1.price == I2.price;
}

public int GetHashCode(Item item)
{
return (item.name?.GetHashCode() ?? 0) ^ item.price.GetHashCode();
}
}

public class Basket
{
public Item[] items;
}

C# Compare Lists with custom object but ignore order

You didn't state clearly which unit test tool you use. Maybe CollectionAssert is the class Microsoft.VisualStudio.TestTools.UnitTesting.CollectionAssert, or maybe it is NUnit.Framework.CollectionAssert, or maybe something else?

So check the documentation of your testing tool, or write here which one you use.

However, it is common for

CollectionAssert.AreEqual( ... );

to check if the collections are the same in the same order, while

CollectionAssert.AreEquivalent( ... );

will check what you want. So use the latter.

Neither of the two methods on CollectionAssert actually uses your override of Equals(object). To use that, write:

Assert.AreEqual( ... );

Edit: I thought Assert.AreEqual(exp, act); would always end up doing exp.Equals(act) which would call your override on AppointmentCollection. But it turns out we end in the private instance method EqualConstraint.ObjectsEqual, and as one sees it checks if the run-time type implements ICollection in which case your override is never used.

Lesson learned: Using Assert.AreEqual can be confusing with collections. Use CollectionAssert.AreEquivalent or CollectionAssert.AreEqual to make your intention clear. You don't have to override Equals on AppointmentCollection if you only need it for testing. If you need it for the application itself and you want to test that, write the test with list1.Equals(list2) literally to make sure your own override is what is tested.

(In any case the override on Appointment is needed, of course.)

Groovy: Compare lists ignoring the order of elements in them

If you want to stay with Lists and not use Sets, you can compare sorted lists:

def list1 = ["ABC", "DEF", "GHI", "JKL"]
def list2 = ["ABC", "DEF", "JKL", "GHI"]

assert list1 != list2
assert list1.sort() == list2.sort()

The suggested Set-based comparison might be dangerous, it the lists contain different numbers of duplicates:

def list1 = ["ABC"]
def list2 = ["ABC", "ABC", "ABC"]

assert list1.sort() != list2.sort()
assert list1 as Set == list2 as Set // GOTCHA!

How to identify if two List string are equal regardless of the order?

You can use SequenceEqual with additional order:

return list1.OrderBy(x => x).SequenceEqual(list2.OrderBy(x => x));

Comparing contents of lists ignoring order

You need to implement the __eq__ and __lt__ methods to allow you to sort the objects and then compare them:

class OBJ:
def __init__(self, a):
self.A = a

def __eq__(self, other):
if not isinstance(other, OBJ):
# don't attempt to compare against unrelated types
return NotImplemented

return self.A == other.A

def __lt__(self, other):
return self.A < other.A

a = [OBJ(1), OBJ(0), OBJ(20), OBJ(-1)]
b = [OBJ(20), OBJ(-1), OBJ(1), OBJ(0)]

test:

sorted(a) == sorted(b)
Output: True

Edit:

The comment in the question made it so that you wanted to check that the objects were exactly the same, not just the same inputs. To do this, just use id() to see if they point to the same exact object

example:

a = OBJ(1)
b = OBJ(-1)
c = OBJ(20)

x = [a,b,c]
y = [c,a,b]
sorted([id(temp) for temp in x]) == sorted([id(temp) for temp in y])
Output: True

however...

a = OBJ(1)
b = OBJ(-1)
c = OBJ(20)
d = OBJ(20) # Same input value as c, but a different object

x = [a,b,c]
y = [d,a,b]
sorted([id(temp) for temp in x]) == sorted([id(temp) for temp in y])
Output: False

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


Related Topics



Leave a reply



Submit