avdnowhere
avdnowhere

Reputation: 177

How to rank a list with original order in c#

I want to make a ranking from a list and output it on original order.

This is my code so far:

     var data = new[] { 7.806468478, 7.806468478, 7.806468478, 7.173501754, 7.173501754, 7.173501754, 3.40877696, 3.40877696, 3.40877696, 
            4.097010736, 4.097010736, 4.097010736, 4.036494085, 4.036494085, 4.036494085, 38.94333318, 38.94333318, 38.94333318, 14.43588131, 14.43588131, 14.43588131 };


        var rankings = data.OrderByDescending(x => x)
               .GroupBy(x => x)
               .SelectMany((g, i) =>
                   g.Select(e => new { Col1 = e, Rank = i + 1 }))
               .ToList();

However, the result will be order it from descending:

ranking

What I want is to display by its original order.

e.g.: Rank = 3, Rank = 3, Rank = 3, Rank = 4, Rank = 4, Rank = 4, etc...

Thank You.

Upvotes: 2

Views: 4374

Answers (3)

Slai
Slai

Reputation: 22876

A bit shorter:

var L = data.Distinct().ToList();      // because SortedSet<T> doesn't have BinarySearch :[
L.Sort();

var rankings = Array.ConvertAll(data, 
                                x => new { Col1 = x, Rank = L.Count - L.BinarySearch(x) });

Upvotes: 1

lc.
lc.

Reputation: 116498

Using what you have, one method would be to keep track of the original order and sort a second time (ugly and potentially slow):

var rankings = data.Select((x, i) => new {Item = x, Index = i})
       .OrderByDescending(x => x.Item)
       .GroupBy(x => x.Item)
       .SelectMany((g, i) =>
           g.Select(e => new { 
               Index = e.Index, 
               Item = new { Col1 = e.Item, Rank = i + 1 }
           }))
       .OrderBy(x => x.Index)
       .Select(x => x.Item)
       .ToList();

I would instead suggest creating a dictionary with your rankings and joining this back with your list:

var rankings = data.Distinct()
                   .OrderByDescending(x => x)
                   .Select((g, i) => new { Key = g, Rank = i + 1 })
                   .ToDictionary(x => x.Key, x => x.Rank);

var output = data.Select(x => new { Col1 = x, Rank = rankings[x] })
                 .ToList();

As @AntonínLejsek kindly pointed out, replacing the above GroupBy call with Distinct() is the way to go.

Note doubles are not a precise type and thus are really not a good candidate for values in a lookup table, nor would I recommend using GroupBy/Distinct with a floating-point value as a key. Be mindful of your precision and consider using an appropriate string conversion. In light of this, you may want to define an epsilon value and forgo LINQ's GroupBy entirely, opting instead to encapsulate each data point into a (non-anonymous) reference type, then loop through a sorted list and assign ranks. For example (disclaimer: untested):

class DataPoint
{
    decimal Value { get; set; }
    int Rank { get; set; }
}

var dataPointsPreservingOrder = data.Select(x => new DataPoint {Value = x}).ToList();

var sortedDescending = dataPointsPreservingOrder.OrderByDescending(x => x.Value).ToList();

var epsilon = 1E-15; //use a value that makes sense here

int rank = 0;
double? currentValue = null;

foreach(var x in sortedDescending)
{
    if(currentValue == null || Math.Abs(x.Value - currentValue.Value) > epsilon) 
    {
        currentValue = x.Value;
        ++rank;
    }
    x.Rank = rank;
}

Upvotes: 7

Nico
Nico

Reputation: 12683

From review of the data you will need to iterate twice over the result set.

The first iteration will be to capture the rankings as.

var sorted = data
    .OrderByDescending(x => x)
    .GroupBy(x => x)
    .Select((g, i) => new { Col1 = g.First(), Rank = i + 1 })
    .ToList();

Now we have a ranking of highest to lowest with the correct rank value. Next we iterate the data again to find where the value exists in the overall ranks as:

var rankings = (from i in data
                let rank = sorted.First(x => x.Col1 == i)
                select new
                {
                    Col1 = i,
                    Rank = rank.Rank
                }).ToList();

This results in a ranked list in the original order of the data.

Upvotes: 1

Related Questions