Is there a built-in way to compare IEnumerable <T> (by their elements)?

I would like to compare lists of elements of this type to see which list is larger.

new BuiltInComparer<IEnumerable<int>>().Compare( new[] {3,2,3}, new[] {1,2,3}) 

... will return 1

 new BuiltInComparer<IEnumerable<int>>().Compare( new[] {1,2,3}, new[] {1,2,4}) 

... will return -1, etc.

Is there such a built-in comparator?

+2
source share
4 answers

I donโ€™t think that something is built into the framework - and, as Eric says, you did not specify comparison criteria. If you mean "compare the element-wise in a natural way and assume that the element is" missing "less than any existing element" (i.e., a longer sequence outperforms a shorter subsequence, if possible), then something like that this is:

 public int SequenceCompare<T>(IEnumerable<T> source1, IEnumerable<T> source2) { // TODO: Parameter validation :) // You could add an overload with this as a parameter IComparer<T> elementComparer = Comparer<T>.Default; using (IEnumerator<T> iterator1 = source1.GetEnumerator()) using (IEnumerator<T> iterator2 = source2.GetEnumerator()) { while (true) { bool next1 = iterator1.MoveNext(); bool next2 = iterator2.MoveNext(); if (!next1 && !next2) // Both sequences finished { return 0; } if (!next1) // Only the first sequence has finished { return -1; } if (!next2) // Only the second sequence has finished { return 1; } // Both are still going, compare current elements int comparison = elementComparer.Compare(iterator1.Current, iterator2.Current); // If elements are non-equal, we're done if (comparison != 0) { return comparison; } } } } 
+10
source

More polished version:

 public static class EnumerableExtensions { /// <summary> /// Performs lexical comparison of 2 IEnumerable collections holding elements of type T. /// </summary> /// <typeparam name="T">Type of collection elements.</typeparam> /// <param name="first">The first collection to compare.</param> /// <param name="second">The second collection to compare.</param> /// <returns>A signed integer that indicates the relative values of a and b: /// Less than zero: first is less than second; /// Zero: first is equal to second; /// Greater than zero: first is greater than second. /// </returns> /// <remarks> /// Can be called as either static method: EnumerableExtensions.Compare(a, b) or /// extension method: a.Compare(b). /// </remarks> public static int Compare<T>(this IEnumerable<T> first, IEnumerable<T> second) { // If one of collection objects is null, use the default Comparer class // (null is considered to be less than any other object) if (first == null || second == null) return Comparer.Default.Compare(first, second); var elementComparer = Comparer<T>.Default; int compareResult; using (var firstEnum = first.GetEnumerator()) using (var secondEnum = second.GetEnumerator()) { do { bool gotFirst = firstEnum.MoveNext(); bool gotSecond = secondEnum.MoveNext(); // Reached the end of collections => assume equal if (!gotFirst && !gotSecond) return 0; // Different sizes => treat collection of larger size as "greater" if (gotFirst != gotSecond) return gotFirst ? 1 : -1; compareResult = elementComparer.Compare(firstEnum.Current, secondEnum.Current); } while (compareResult == 0); } return compareResult; } } 
+3
source

If you're on .NET 4 (and it doesn't look like you), I think you could do something smart with Enumerable.Zip . Sort of:

 var r = x.Zip(y, comparer.Compare).FirstOrDefault(c => c != 0); 

although I cannot see now how to deal effectively with the case when the shorter one is the same as the longer one as much as possible.

Edit: If you are only comparing arrays (or else don't care about measuring your collections twice), then I think you can simply add:

 if (r == 0) { r = int.Compare(x.Count(), y.Count()); } 

You can even combine them as:

 var r = x.Zip(y, comparer.Compare) .Concat(new [] { int.Compare(x.Count(), y.Count()) }) .FirstOrDefault(c => c != 0) 

(And if you're on .NET 3.5, add the Zip extension method because it is easy to write and seriously useful everywhere! I donโ€™t know why it was not included in the initial version of Linq.)

+2
source

There is no built-in comparator. However, this requirement often arises. I covered this topic in detail in the SequenceComparer<T> article; here is a simplified implementation:

 public class SequenceComparer<TElement> : Comparer<IEnumerable<TElement>> { private readonly IComparer<TElement> _elementComparer; public SequenceComparer(IComparer<TElement> elementComparer = null) { _elementComparer = elementComparer ?? Comparer<TElement>.Default; } public override int Compare(IEnumerable<TElement> x, IEnumerable<TElement> y) { // Get enumerators to iterate over both sequences in sync. using (IEnumerator<TElement> xEnumerator = x.GetEnumerator()) using (IEnumerator<TElement> yEnumerator = y.GetEnumerator()) { // Advance both enumerators to their next element, // until at least one passes the end of its sequence. bool xMove, yMove; while ((xMove = xEnumerator.MoveNext()) && (yMove = yEnumerator.MoveNext())) { // Compare the current pair of elements across the two sequences, // seeking element inequality. int elementComparison = _elementComparer.Compare(xEnumerator.Current, yEnumerator.Current); if (elementComparison != 0) return elementComparison; } // Determine the relative length of the two sequences based on the final values of xMove and yMove. return xMove.CompareTo(yMove); } } } 
0
source

All Articles