James Wilson
James Wilson

Reputation: 5150

How to grab the index from a list using LINQ

I have a list I am populating with total sales from a team.

lstTeamSales.OrderBy(x => x.TotalSales);

This list has an int userID and a decimal totalSales.

I order it by totalSales. How can I at that point figure out the rank for the person logged in?

I know I can compare the person who is logged in by his userID to that of the userID in the list. If he is #3 in sales I need to return an int of his rank which would be Rank 3.

Upvotes: 1

Views: 1426

Answers (4)

Sergey Krusch
Sergey Krusch

Reputation: 1938

The question can be rephrased to "How do I get index of element in IEnumerable". Here is the answer: How to get index using LINQ? Here is how to use it:

int rank = lstTeamSales.OrderBy(x => x.TotalSales).FindIndex(x => x.userID == currentUserID);

And this will be slightly more efficient than Select based approaches.

Update

It appears .FindIndex is not supported for LINQ. Any idea how to implement that functionality?

I may have figured it out testing it now. I just added .ToList() after the ORderBy().

No-no-no-no! It kills the whole idea :( The idea is to add extension method FindIndex to IEnumerable. And then use it. See example:

static class FindIndexEnumerableExtension
{
    public static int FindIndex<T>(this IEnumerable<T> items, Func<T, bool> predicate)
    {
        if (items == null) throw new ArgumentNullException("items");
        if (predicate == null) throw new ArgumentNullException("predicate");

        int retVal = 0;
        foreach (var item in items)
        {
            if (predicate(item)) return retVal;
            retVal++;
        }
        return -1;
    }
}

class YourClass
{
    void YourMethod()
    {
        lstTeamSales.OrderBy(x => x.TotalSales).FindIndex(x => x.UserID == currentUserID);
    }
}

After you define class FindIndexEnumerableExtension with FindIndex extension method, you can use this method anywhere in your code. All you need is just add using directive with module where FindIndexEnumerableExtension is defined. This is, basically, how LINQ works.

If you don't want to go with this solution then, at least, convert lstTeamSales to List before sorting it. And sort it using List<>.Sort() method.

Upvotes: 2

nmclean
nmclean

Reputation: 7724

The Select((item, index) => ...) form allows for this (as shown by Simon), however as DMac mentions you probably want to consider duplicates. To incorporate this in a Select, you could use GroupBy:

lstTeamSales
    .OrderByDescending(x => x.TotalSales).GroupBy(x => x.TotalSales)
    .Select((group, i) => new {
        Rank = i + 1,
        Users = group.Select(x => x.UserId)
    })

This would provide you with a list of ranks along with the lists of users who have that rank. Or you could flatten this with SelectMany, to get each user with its rank:

lstTeamSales
    .OrderByDescending(x => x.TotalSales).GroupBy(x => x.TotalSales)
    .SelectMany((x, i) => new {
        Rank = i + 1,
        User = x.UserId
    })

You could filter this sequence to find users, but if you only want to look up a specific user's rank, then DMac's solution is the most direct. The above would be more useful for example if you wanted to list the top 5 sellers (see Take).

Upvotes: 0

DMac the Destroyer
DMac the Destroyer

Reputation: 5290

Given a list of total sales, lstTeamSales and a number representing the sales you wish to find the rank for, userSales, what you'll need is the number of total sales in lstTeamSales that exceed userSales. If it's rank you want, then you'd probably want to exclude ties in the rank (i.e. if the top two sales numbers are both 1000, then they'd both be ranked 1)

You can do this simply by projecting only the sales numbers with Select, remove ties with a Distinct call, then use Count:

lstTeamSales.Select(x => x.TotalSales).Distinct().Count(x => x > userSales)

That would give you the total number of sales that are higher than the current user. From there, the rank of the current user is one above that number:

var rank = 1 + lstTeamSales.Select(x => x.TotalSales).Distinct().Count(x => x > userSales)

Upvotes: 1

Simon Belanger
Simon Belanger

Reputation: 14870

You can use the select extenstion that takes a Func<TSource, Int32, TResult> (or the Expression equivalent) like so:

var userId = /* the userId */;
lstTeamSales.OrderBy(x => x.TotalSales).Select((x, i) => new 
{
    x.UserId,
    x.TotalSales,
    Rank = i + 1
}).FirstOrDefault(x => x.UserId == theUserId);

This will return an object with the user id, the total sales and the rank where the user id is fixed. It will return null if there is no entity where UserId = theUserId in the collection.

The index (i in the example) is 0-based. Adjust as needed.

Upvotes: 1

Related Questions