Search code examples
c#linq

Group students with similar scores (5 points up/ down)


I want to group students with similar scores with 5 points up/ down.

public class Students
{
    public string Name {get; set;}
    public int Marks {get; set;}
    public int GroupID {get; set;}
}

public static void Main()
{
    List<Students> students = new List<Students>();
    
    students.Add(new Students { Name = "Aakash", Marks=89, GroupID=0 });
    students.Add(new Students { Name = "Prakash", Marks=85, GroupID=0  });
    students.Add(new Students { Name = "Ramesh", Marks=40, GroupID=0  });
    students.Add(new Students { Name = "Neha", Marks=95, GroupID=0 });
    students.Add(new Students { Name = "Suresh", Marks=93, GroupID=0 });
}

Expected Output:

GroupID 1:
Aakash, Prakash

Group 2:
Ramesh
    
GroupID 3:
Neha, Suresh

What happens when students with marks of 4, 8, 12, 16, 20 & 24?

They would be split into different groups so that each group has a range of 5 points difference at maximum:

  • 4 & 8 in Group 1
  • 12 & 16 in Group 2
  • 20 & 24 in Group 3.

Solution

  • If you want to determine the range of marks for each group based on the highest mark in that group, you may write something like this:

    var ordered = students.OrderByDescending(s => s.Marks).ToList();
    int groupId = 0;
    int nextIndex = 0;
    while (nextIndex < ordered.Count)
    {
        groupId++;
        var firstInGroup = ordered[nextIndex];
        var group = ordered.Skip(nextIndex).
                            TakeWhile(s => firstInGroup.Marks - s.Marks <= 5).ToList();
        group.ForEach(s => s.GroupID = groupId);
        nextIndex += group.Count;
    }
    

    To make this reusable and to allow grouping to happen in either direction, you can turn this into a helper method like so:

    enum MarkGroupingOption { HighestToLowest, LowestToHighest }
    
    static void SetGroupIds(List<Students> students, int marksThreshold, 
                            MarkGroupingOption groupingOption)
    {
        var ordered = (groupingOption == MarkGroupingOption.HighestToLowest
            ? students.OrderByDescending(s => s.Marks).ToList()
            : students.OrderBy(s => s.Marks).ToList());
    
        int groupId = 0;
        int nextIndex = 0;
        while (nextIndex < ordered.Count)
        {
            groupId++;
            var firstInGroup = ordered[nextIndex];
            var remaining = ordered.Skip(nextIndex);
            var group = (groupingOption == MarkGroupingOption.HighestToLowest
                ? remaining.TakeWhile(s => firstInGroup.Marks - s.Marks <= marksThreshold)
                : remaining.TakeWhile(s => s.Marks - firstInGroup.Marks <= marksThreshold)
                ).ToList();
            group.ForEach(s => s.GroupID = groupId);
            nextIndex += group.Count;
        }
    }
    

    Usage:

    SetGroupIds(students, 5, MarkGroupingOption.HighestToLowest);
    
    SetGroupIds(students, 5, MarkGroupingOption.LowestToHighest);