What is the best way to determine if two List <T> objects contain the same set of values, even if they are not in the same order?
I have two objects List<T>(where Tis the same type for both objects) and I need to determine if they contain the same set of values, even if the values ββare not in the same order.
Do objects have any built-in mechanisms to accomplish this, or do I need to write my own algorithm?
Or maybe I should use a different type of collection, not List<T>?
If I wrote my own algorithm, it would probably consist of the following steps: I will try to optimize this in the final version if I go this route:
- Do both collections have the same number of values? If not return false.
- Count how many times each value appears in each collection, returns false if the counts are not equal.
- If I get to the end of both collections without any disparity in count values, return true.
I know that there are some caveats to this, for example, the fact that T must be comparable - I use the default comparison at the moment (for example .Equals()) with the corresponding restrictions defined for the typical type.
, SetEquals . HashSet , , , :
public static bool SetEquals<T>(this IEnumerable<T> first, IEnumerable<T> second,
IEqualityComparer<T> comparer = null)
{
return new HashSet<T>(second, comparer ?? EqualityComparer<T>.Default)
.SetEquals(first);
}
, , , , , , , . , , . , :
public static bool BagEquals<T>(
this IEnumerable<T> first,
IEnumerable<T> second)
{
Func<IEnumerable<T>, IEnumerable<KeyValuePair<T, int>>> groupItems =
sequence => sequence.GroupBy(item => item,
(key, items) => new KeyValuePair<T, int>(key, items.Count()));
return groupItems(first)
.SetEquals(groupItems(second));
}
Here is a reimplementation CollectionAssert.AreEquivalent(the link code was decompiled with DotPeek), however, instead of throwing an exception, it returns bool.
public class CollectionMethods
{
public static bool AreEquivalent(ICollection expected, ICollection actual)
{
//We can do a few quick tests we can do to get a easy true or easy false.
//Is one collection null and one not?
if (Object.ReferenceEquals(expected, null) != Object.ReferenceEquals(actual, null))
return false;
//Do they both point at the same object?
if (Object.ReferenceEquals(expected, actual))
return true;
//Do they have diffrent counts?
if (expected.Count != actual.Count)
return false;
//Do we have two empty collections?
if (expected.Count == 0)
return true;
//Ran out of easy tests, now have to do the slow work.
int nullCount1;
Dictionary<object, int> elementCounts1 = CollectionMethods.GetElementCounts(expected, out nullCount1);
int nullCount2;
Dictionary<object, int> elementCounts2 = CollectionMethods.GetElementCounts(actual, out nullCount2);
//One last quick check, do the two collections have the same number of null elements?
if (nullCount2 != nullCount1)
{
return false;
}
//Check for each element and see if we see them the same number of times in both collections.
foreach (KeyValuePair<object,int> kvp in elementCounts1)
{
int expectedCount = kvp.Value;
int actualCount;
elementCounts2.TryGetValue(key, out actualCount);
if (expectedCount != actualCount)
{
return false;
}
}
return true;
}
private static Dictionary<object, int> GetElementCounts(ICollection collection, out int nullCount)
{
Dictionary<object, int> dictionary = new Dictionary<object, int>();
nullCount = 0;
foreach (object key in (IEnumerable)collection)
{
if (key == null)
{
++nullCount;
}
else
{
int num;
dictionary.TryGetValue(key, out num);
++num;
dictionary[key] = num;
}
}
return dictionary;
}
}