I wrote these extension methods just a few days ago:
#region RankBy public static IEnumerable<TResult> RankBy<TSource, TKey, TResult>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, int, TResult> resultSelector) { return source.RankBy(keySelector, null, false, resultSelector); } public static IEnumerable<TResult> RankBy<TSource, TKey, TResult>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer, Func<TSource, int, TResult> resultSelector) { return source.RankBy(keySelector, comparer, false, resultSelector); } public static IEnumerable<TResult> RankByDescending<TSource, TKey, TResult>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer, Func<TSource, int, TResult> resultSelector) { return source.RankBy(keySelector, comparer, true, resultSelector); } public static IEnumerable<TResult> RankByDescending<TSource, TKey, TResult>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, int, TResult> resultSelector) { return source.RankBy(keySelector, null, true, resultSelector); } private static IEnumerable<TResult> RankBy<TSource, TKey, TResult>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer, bool descending, Func<TSource, int, TResult> resultSelector) { comparer = comparer ?? Comparer<TKey>.Default; var grouped = source.GroupBy(keySelector); var ordered = descending ? grouped.OrderByDescending(g => g.Key, comparer) : grouped.OrderBy(g => g.Key, comparer); int totalRank = 1; foreach (var group in ordered) { int rank = totalRank; foreach (var item in group) { yield return resultSelector(item, rank); totalRank++; } } } #endregion #region DenseRankBy public static IEnumerable<TResult> DenseRankBy<TSource, TKey, TResult>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, int, TResult> resultSelector) { return source.DenseRankBy(keySelector, null, false, resultSelector); } public static IEnumerable<TResult> DenseRankBy<TSource, TKey, TResult>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer, Func<TSource, int, TResult> resultSelector) { return source.DenseRankBy(keySelector, comparer, false, resultSelector); } public static IEnumerable<TResult> DenseRankByDescending<TSource, TKey, TResult>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer, Func<TSource, int, TResult> resultSelector) { return source.DenseRankBy(keySelector, comparer, true, resultSelector); } public static IEnumerable<TResult> DenseRankByDescending<TSource, TKey, TResult>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, int, TResult> resultSelector) { return source.DenseRankBy(keySelector, null, true, resultSelector); } private static IEnumerable<TResult> DenseRankBy<TSource, TKey, TResult>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer, bool descending, Func<TSource, int, TResult> resultSelector) { comparer = comparer ?? Comparer<TKey>.Default; var grouped = source.GroupBy(keySelector); var ordered = descending ? grouped.OrderByDescending(g => g.Key, comparer) : grouped.OrderBy(g => g.Key, comparer); int rank = 1; foreach (var group in ordered) { foreach (var item in group) { yield return resultSelector(item, rank); } rank++; } } #endregion
You can use them as follows:
var rankedPlayers = players.RankByDescending( p => p.Score, (p, r) => new { Rank = r, Player = p });
The difference between RankBy and DenseRankBy is that RankBy creates βspacesβ (for example, 1,1,3,3,3,6 ...), whereas DenseRankBy does not (1,1, 2,2,2,3 ...)