Extension method for ordering an IEnumerable<T> with custom ranking. See blog post for description: http://notherdev.blogspot.com/2013/02/extension-for-ordering-enumerable-with-ranking.html
// Extension method for ordering an IEnumerable<T> with custom ranking. | |
// http://notherdev.blogspot.com/2013/02/extension-for-ordering-enumerable-with-ranking.html | |
public class Ranked<T> | |
{ | |
public Ranked(T item, int rank) | |
{ | |
Item = item; | |
Rank = rank; | |
} | |
public T Item { get; private set; } | |
public int Rank { get; private set; } | |
} | |
public class SortDefinition<T> | |
{ | |
public SortDefinition(Func<T, object> selector, bool isDescending = false) | |
{ | |
Selector = selector; | |
IsDescending = isDescending; | |
} | |
public Func<T, object> Selector { get; private set; } | |
public bool IsDescending { get; private set; } | |
} | |
public static class OrderAndRankByEnumerableExtension | |
{ | |
public static IEnumerable<Ranked<T>> OrderAndRankBy<T>( | |
this IEnumerable<T> source, | |
IEnumerable<SortDefinition<T>> sortAndRank, | |
IEnumerable<SortDefinition<T>> sortOnly) | |
{ | |
if (source == null) | |
throw new ArgumentNullException("source"); | |
var sortAndRankArray = sortAndRank.ToArray(); | |
var allSorters = sortAndRankArray.Concat(sortOnly).ToArray(); | |
if (allSorters.Length == 0) | |
return CreateRanking(source, EqualityComparer<T>.Default); | |
var ordered = SortSequence(source, allSorters); | |
return CreateRanking(ordered, new SortersEqualityComparer<T>(sortAndRankArray)); | |
} | |
private static IOrderedEnumerable<T> SortSequence<T>( | |
IEnumerable<T> source, SortDefinition<T>[] sorters) | |
{ | |
var ordered = sorters[0].IsDescending | |
? source.OrderByDescending(sorters[0].Selector) | |
: source.OrderBy(sorters[0].Selector); | |
for (var i = 1; i < sorters.Length; ++i) | |
{ | |
var sorter = sorters[i]; | |
ordered = sorters[i].IsDescending | |
? ordered.ThenByDescending(sorter.Selector) | |
: ordered.ThenBy(sorter.Selector); | |
} | |
return ordered; | |
} | |
private static IEnumerable<Ranked<T>> CreateRanking<T>( | |
IEnumerable<T> ordered, IEqualityComparer<T> comparer) | |
{ | |
using (var it = ordered.GetEnumerator()) | |
{ | |
if (!it.MoveNext()) | |
yield break; | |
// return first value with rank 1 | |
var current = it.Current; | |
var ranked = new Ranked<T>(current, 1); | |
yield return ranked; | |
var previous = ranked; | |
var nextInOrder = 2; | |
while (it.MoveNext()) | |
{ | |
// and all the other values with either previous value for equal items | |
// or next rank in order | |
current = it.Current; | |
ranked = new Ranked<T>(current, comparer.Equals(previous.Item, current) | |
? previous.Rank | |
: nextInOrder); | |
yield return ranked; | |
previous = ranked; | |
++nextInOrder; | |
} | |
} | |
} | |
private class SortersEqualityComparer<T> : EqualityComparer<T> | |
{ | |
private readonly SortDefinition<T>[] _sorters; | |
public SortersEqualityComparer(IEnumerable<SortDefinition<T>> sorters) | |
{ | |
_sorters = sorters.ToArray(); | |
} | |
public override bool Equals(T x, T y) | |
{ | |
return _sorters.All(s => s.Selector(x).Equals(s.Selector(y))); | |
} | |
public override int GetHashCode(T obj) | |
{ | |
return obj == null ? 0 : obj.GetHashCode(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment