I want to get the top X scores on LINQ and rank them.

Asked 2 years ago, Updated 2 years ago, 107 views

I'm trying to achieve the common "Top X Points" in LINQ.

class member {
    public string name {get;set;}
    public int point {get;set;}
}

An instance array similar to the one shown in

 var SelectedMembers = AllMembers
    .OrderByDescending(mem=>mem.Point)
    .Take (100);

Then, if there are many people with the same points, I don't think it can be extracted correctly.
This is the order of common points
1.AAA
2.BBB
2. CCC
4.DDD 4.EEE
4.FFF
As shown in , how can I extract more than a specified number of people and make a group list with the same points in mind?

c# linq

2022-09-30 20:47

5 Answers

If the number of points is counted as high as +1, the number of points will be ranked.

varq1=AllMembers.OrderByDescending(m=>m.Point).Select(m)=>
    new
    {
        Point = m.Point,
        Name = m.Name,
        // You can check the ranking by counting the top number of points as they are
        Rank=AllMembers.Count(m2=>(m2.Point>m.Point))+1
    }).Where(ano=>ano.Rank<=4);

foreach (varano in q1)
{
    Console.WriteLine(string.Format("Rank={0}\tPoint={1}\tName={2}",ano.Rank,ano.Point,ano.Name));
}

Alternatively, the Nth Point will be ranked Nth by arranging the points in descending order without considering duplication, so try to reduce the calculation a little by checking the points first.

var po=AllMembers.OrderByDescending(m2=>m2.Point).Skip(4-1).FirstOrDefault().Point;
varq2=AllMembers.Where(m=>m.Point>=po).OrderByDescending(m=>m.Point).Select(m)=>
    new
    {
        Point = m.Point,
        Name = m.Name,
        Rank=AllMembers.Count(m2=>(m2.Point>m.Point))+1
    });

You can do that.


2022-09-30 20:47

If you only do LINQ, it's either unreasonable to hold the condition or increase the amount of calculation, so you should use foreach normally.

intrank=0, count=0, prev=-1;//← Set the initial value to an unlikely value as Point
foreach(varmin AllMembers.OrderByDescending(_=>_.Point))
{
    count++;
    if(m.Point!=prev)
    {
        if (count > 100)
        {
            break;
        }
        rank=count;
    }
    prev = m.Point;

    Console.WriteLine("{0,-3}{1,-3}{2}{3}", count, rank, m.Name, m.Point);
}

If you want to increase the LINQ portion more, overload the indexer.

intrank=0,prev=-1;
AllMembers.OrderByDescending(_=>_.Point)
            .Select(m,i)=>new
                    {
                        Rank=prev==(prev=m.Point)?rank:(rank=i+1),
                        Value = m
                    })
            .Where(_=>_.Rank<=100);

You can also write

If the other party is an ORM such as EntityFramework,

to reduce traffic volume.
var threshold=AllMembers.OrderByDescending(_=>_.Point)
                            .Take (100)
                            .Min(_=>(int?)_.Point);
AllMembers.Where(_=>_.Point>=threshold);

I think a policy like this would be good.


2022-09-30 20:47

The ranking matches "How many people are above you +1", so you can do it by looking at each ranking.

 var ranking = AllMembers
    .OrderByDescending(_=>_.Point)
    .GroupBy(_=>_.Point)
    .Select(group=>new
        {
            Rank=AllMembers.Count(_=>_.Point>group.Key) +1,
            Members=group
        });

// Ranking.Where(_=>_.Rank<=4) if you want to extract 4th or higher.

If you add the number of people in order from the top to the top, you just need to count the same ranking, but I think it's troublesome to do this with standard LINQ without using foreach.

For example, if you use Rx or Ix Scan(), you can write like this.

var ranking=list
    .OrderByDescending(_=>_.Point)
    .GroupBy(_=>_.Point)
    .Scan(new)
        {
            Rank = 1,
            Members = new List <Member > ( )
        },
        (prev, group) = > new
            {
                Rank=prev.Rank+prev.Members.Count(),
                Members=group.ToList()
            });


2022-09-30 20:47

var members=new Member[]
{
    new Member() {Name="H", Point=1} ,
    new Member() {Name="G", Point=1} ,
    new Member() {Name="F", Point=2},
    new Member() {Name="E", Point=2},
    new Member() {Name="D", Point=3},
    new Member() {Name="C", Point=3},
    new Member() {Name="B", Point=4},
    new Member() {Name="A", Point=4},
};

var threshold = 5;

var result=members
    .OrderByDescending(x=>x.Point)
    .Select ((Value,Index)=>new {Value,Index})
    .GroupBy(x=>x.Value.Point)
    .Select(x=>new{Rank=x.First().Index+1, Items=x.Select(y=>y.Value)})
    .Where(x=>x.Rank<=threshold);

foreach(var group in result)
{
    Console.WriteLine(group.Rank.ToString());

    foreach(varitem in group.Items.OrderBy(x=>x.Name))
    {
        Console.WriteLine(item.Name+item.Point);
    }
}

Console.ReadLine();


2022-09-30 20:47

You can do it by dividing the LINQ into two and using TakeWhile.

var AllMembers=new[]{
    new {Name="AAA", Point=1} ,
    new {Name="BBB", Point=2},
    new {Name="CCC", Point=2},
    new {Name="DDD", Point=4},
    new {Name="EEE", Point=4},
    new {Name="FFF", Point=4}
};

varSortedMembers= 
    AllMembers.OrderByDescending(m=>m.Point).ToArray();

// take up to a designated number of people
// (Even if the designated number of people is reached, if there is a member with the same point, take it out.)
varSelectedMembers= 
    SortedMembers.TakeWhile(m,i)=>i<4||m.Point==SortedMembers[i-1].Point);

Console.WriteLine(string.Join(Environment.NewLine, SelectedMembers)));


2022-09-30 20:47

If you have any answers or tips


© 2024 OneMinuteCode. All rights reserved.