Question

I am working on creating calculations from a spreadsheet into C#, and I was wondering if C# has a similar method to Rank in Excel?

Rank in Excel

Returns the rank of a number in a list of numbers. The rank of a number is its size relative to other values in a list. (If you were to sort the list, the rank of the number would be its position.)

Syntax

RANK(number,ref,order)

Number is the number whose rank you want to find.

Ref is an array of, or a reference to, a list of numbers. Nonnumeric values in ref are ignored.

Order is a number specifying how to rank number.

If order is 0 (zero) or omitted, Microsoft Excel ranks number as if ref were a list sorted in descending order. If order is any nonzero value, Microsoft Excel ranks number as if ref were a list sorted in ascending order.

The same can be achieved through code, but I just wanted to check if there was anything I was missing first.

Was it helpful?

Solution

You can, sort of.

        SortedList<int, object> list = new SortedList<int, object>();
        // fill with unique ints, and then look for one
        int rank = list.Keys.IndexOf(i);

Rank will be an ascending, zero-based position.

You could pretty it up by writing an extension method:

public static class Extensions
{
    public static int Rank(this int[] array, int find)
    {
        SortedList<int, object> list = new SortedList<int, object>();
        for (int i = 0; i < array.Length; i++)
        {
            list.Add(array[i], null);
        }
        if (list.ContainsKey(find))
        {
            return list.Keys.IndexOf(find);
        }
        else
        {
            return -1;
        }
    }
}

And use it like:

    int[] ints = new int[] { 2, 7, 6, 3, 9, 12 };
    int rank = ints.Rank(2);

...but I'm not convinced its the most sensible thing to do.

OTHER TIPS

To get the equivalent of RANK you'll need to get the minimum index of each item when you group:

var ranks = list.OrderBy(x => x)
                 .Select((x, i) => new {x, i = i+1})  // get 1-based index of each item
                 .GroupBy(xi => xi.x)        // group by the item
                 .Select(g => new {rank = g.Min(xi => xi.i), items = g})  // rank = min index of group
                 .SelectMany(g => g.items, (g, gg) => new {g.rank, gg.i}) ;   // select rank and item

or if you'rs grouping by the property of a class:

var ranks = list.OrderBy(x => x.{some property})
                 .Select((x, i) => new {x, i = i+1})  // get 1-based index of each item
                 .GroupBy(xi => xi.x.{some property})        // group by the item's property
                 .Select(g => new {rank = g.Min(xi => xi.i), items = g})  // rank = min index of group
                 .SelectMany(g => g.items, (g, gg) => new {g.rank, gg.i}) ;   // select rank and item

This works for me so far (and it is simpler)

public static int Rank<T>(T value, IEnumerable<T> data)
{
    return data.OrderByDescending(x => x).ToList().IndexOf(value) + 1;
}

I used T so it can take all numeric types (int/double/decimal).

The usage is similar to Excel

int[] data = new[] { 3, 2, 2, 3, 4 };
int rank = Rank(3, data); // returns 2

I hope I didn't miss anything

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top