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 "gaps" (e.g. 1,1,3,3,3,6...) whereas DenseRankBy
does not (1,1,2,2,2,3...)