Optimization of custom rank function in C#

Viewed 151

I have written a code for students class to Rank them according to their marks. The code works accurately and gives the result as

Name:B  Marks:30    Rank:1
Name:C  Marks:30    Rank:1
Name:A  Marks:20    Rank:3
Name:D  Marks:10    Rank:4

But I need it to be optimized so it wont take too much of time to be processed. Below is the code

public List<Students> GetRanks(List<Students> students)
{
    List<Students> rStudents = new List<Students>();
    students = students.OrderByDescending(a => a.marks).ToList();
    for (int i = 0; i < students.Count; i++)
    {
        Students stu = new Students();
        stu.Id = students[i].Id;
        stu.Name = students[i].Name;
        stu.marks = students[i].marks;

        if (i > 0 && students[i].marks == students[i - 1].marks)
        {
            stu.rank = rStudents.Select(a => a.rank).LastOrDefault();
        }
        else
        {
            stu.rank = i + 1;
        }
        rStudents.Add(stu);
    }
    return rStudents;
}

List<Students> students = new List<Students>() {
new Students() { Id = 1, Name = "A", marks = 20 },
new Students() { Id = 1, Name = "B", marks = 30 },
new Students() { Id = 1, Name = "C", marks = 30 },
new Students() { Id = 1, Name = "D", marks = 10 },
};

List<Students> rStudents = GetRanks(students);
            
            
public class Students
{
    public int Id { get; set; }
    public string Name { get; set; }
    public double marks { get; set; }
    public int rank { get; set; }
}


foreach (Students s in GetRanks(students))
{
    Console.WriteLine($"Name:{s.Name}\tMarks:{s.marks}\tRank:{s.rank}");
}
3 Answers

Remove unnecessary resource.and using yield will have performance impact in large data. Fewer line and more readable. If you are fetching data from DataBase it's better idea sort them there then fetch them.

  public static IEnumerable<Students> GetRanks(List<Students> students)
  {
      List<Students> rStudents = students.OrderByDescending(a => a.marks).ToList();
      for (int i = 0; i < rStudents.Count; i++)
      {
        if (i > 0 && rStudents[i].marks == rStudents[i - 1].marks)
          {
              rStudents[i].rank = rStudents[i - 1].rank;
          }
          else
          {
              rStudents[i].rank = i + 1;
          }
          yield return rStudents[i];
      }
  }

This should be pretty darn fast:

var rank = 1;
students
    .GroupBy(x => x.marks)
    .OrderByDescending(x => x.Key)
    .ToList()
    .ForEach(xs =>
    {
        xs.ToList().ForEach(x => x.rank = rank);
        rank += xs.Count();
    });

With your sample data I get:

students

Here's a non-destructive version of the code:

public static List<Students> GetRanks(List<Students> students)
{
    var rank = 1;
    return
        students
            .OrderByDescending(x => x.marks)
            .GroupBy(x => x.marks)
            .SelectMany(xs =>
            {
                var r = rank;
                rank += xs.Count();
                return xs.Select(x => new Students()
                {
                    Id = x.Id,
                    Name = x.Name,
                    marks = x.marks,
                    rank = r,
                });
            })
            .ToList();
}

I measured this using 100_000 randomly created students. My code completed in 110 milliseconds. The original code in the question took 64_119 milliseconds.

Yes, this code was nearly 600x faster.

More then 50% improvement if have large collect with a new method called GetRanksV1

namespace StudentPerformance
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            List<Students> students = new List<Students>();
            Random _random = new Random();
            for (int i = 1; i < 10001; i++)
            {
                students.Add(new Students() { Id = i, Name = "A" + i, marks = _random.Next(10, 50) });
            }
            var watch = System.Diagnostics.Stopwatch.StartNew();
            var tmp = GetRanks(students);
            watch.Stop();
            Console.WriteLine($"Execution Time: {watch.ElapsedMilliseconds} ms");
            watch = System.Diagnostics.Stopwatch.StartNew();
            var tmp1 = GetRanksV1(students);
            watch.Stop();
            Console.WriteLine($"Execution Time V1: {watch.ElapsedMilliseconds} ms");
            foreach (Students s in tmp1)
            {
                Console.WriteLine($"Name:{s.Name}\tMarks:{s.marks}\tRank:{s.rank}");
            }
            Console.ReadLine();
        }

        public static List<Students> GetRanks(List<Students> students)
        {
            List<Students> rStudents = new List<Students>();
            students = students.OrderByDescending(a => a.marks).ToList();
            for (int i = 0; i < students.Count; i++)
            {
                Students stu = new Students();
                stu.Id = students[i].Id;
                stu.Name = students[i].Name;
                stu.marks = students[i].marks;

                if (i > 0 && students[i].marks == students[i - 1].marks)
                {
                    stu.rank = rStudents.Select(a => a.rank).LastOrDefault();
                }
                else
                {
                    stu.rank = i + 1;
                }
                rStudents.Add(stu);
            }
            return rStudents;
        }
        public static List<Students> GetRanksV1(List<Students> students)
        {
            int Srno = 0;
            var tmp = students.GroupBy(a => a.marks).OrderByDescending(o => o.Key).Select(s => new { Mark = s.Key, Rand = ++Srno, });
            var values = from s in students
                         join t in tmp on s.marks equals t.Mark
                         select new Students { Id = s.Id, Name = s.Name, marks = s.marks, rank = t.Rand };
            return values.ToList();
        }
    }
    public class Students
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public double marks { get; set; }
        public int rank { get; set; }
    }

}
Related