4

Considering following code:

class Results
{
    public int playerId;
    public int score;
    public int section;
    public int position;
    public Results(int _playerId, int _score, int _section)
    {
        playerId = _playerId;
        score = _score;
        section = _section;
    }
}

public void RankMyResults()
{
    List<Results> myResultList = new List<Results>();

    myResultList.Add(new Results(1,232, 1));
    myResultList.Add(new Results(2,213, 1));
    // Add a lot of more results

    // Iteriate over the items to set the position
}

I want to set position 1 for the highest score in each section, position 2 for second highest and so on.

Also if two people have the same score the positions should look like this

Position  Score   PlayerId Section
1         135     23       1
1         135     43       1
3         131     45       1

As in this example it will skip position 2.

Is there a nice way to use LINQ to do this or for example using some Select, Sorting functionality from the List Object?

My own solution iterate over the list is not good at all!

Enamul Hassan
  • 5,266
  • 23
  • 39
  • 56
StefanE
  • 7,578
  • 10
  • 48
  • 75
  • 1
    Why isn't your solution good? LINQ can simplify some queries a lot, but sometimes normal imperative code using loops is just better. LINQ didn't make loops obsolete. – svick Nov 10 '11 at 16:29
  • You can post your solution and wecan try to simplify (?!) it using LINQ, but this is not always makes sense – sll Nov 10 '11 at 16:30
  • 3
    See [C# Ranking of objects, multiple criteria](http://stackoverflow.com/questions/932300/c-sharp-ranking-of-objects-multiple-criteria) there are few nice solutions – sll Nov 10 '11 at 16:32
  • Why don't you implement `IComparable`? – Daniel A. White Nov 10 '11 at 16:46

2 Answers2

8

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...)

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
0

I modified those great methods above so that they preserve the original order. Ranking methods should not change the order of the elements, they should just rank them and return the elements in the original order of input collection.

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)
{
    var comp0 = comparer ?? Comparer<TKey>.Default;
    var comp = descending ? Comparer<TKey>.Create((x, y) => -comp0.Compare(x, y)) : comp0;

    var keys = source.Select(x => keySelector(x)).ToArray();
    var indexes = Enumerable.Range(0, keys.Length).ToArray();
    Array.Sort<TKey, int>(keys, indexes, comp);

    var groups = new int[keys.Length];
    int group = 0;
    int index = 0;
    for (int j = 1; j < keys.Length; ++j)
    {
        ++index;
        if (comp.Compare(keys[j], keys[j - 1]) != 0)
        {
            group += index;
            index = 0;
        }
        groups[indexes[j]] = group;
    }

    index = 0;
    foreach (var item in source)
    {
        yield return resultSelector(item, groups[index++] + 1);
    }
}


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)
{
    var comp0 = comparer ?? Comparer<TKey>.Default;
    var comp = descending ? Comparer<TKey>.Create((x, y) => -comp0.Compare(x, y)) : comp0;

    var keys = source.Select(x => keySelector(x)).ToArray();
    var indexes = Enumerable.Range(0, keys.Length).ToArray();
    Array.Sort<TKey, int>(keys, indexes, comp);

    var groups = new int[keys.Length];
    int group = 0;
    for (int j = 1; j < keys.Length; ++j)
    {
        if (comp.Compare(keys[j], keys[j - 1]) != 0)
            ++group;
        groups[indexes[j]] = group;
    }

    int index = 0;
    foreach (var item in source)
    {
        yield return resultSelector(item, groups[index++] + 1);
    }
}
Enamul Hassan
  • 5,266
  • 23
  • 39
  • 56
Nytmo
  • 71
  • 1
  • 12