Home > Enterprise >  How to perform group by on object list
How to perform group by on object list

Time:09-17

I have an object list. Object contain another list. I want to perform group by in C# based on inner list.

I am using below code but it is not working.

class Abc
{
    public int id { get; set; }
    public List<xyz> courselist { get; set; }
}

class xyz
{
    public string course { get; set; }
}
      
public void PerformGroupBy 
{
    List<Abc> projects = new List<Abc>();

    var prj1 = new Abc();
    prj1.id = 100;
    prj1.courselist = new List<xyz>();
    prj1.courselist.Add(new xyz { course = "imr101" });
    prj1.courselist.Add(new xyz { course = "imr102" });
    projects.Add(prj1);

    var prj2 = new Abc();
    prj2.id = 101;
    prj2.courselist = new List<xyz>();
    prj2.courselist.Add(new xyz { course = "imr201" });
    prj2.courselist.Add(new xyz { course = "imr102" });
    projects.Add(prj2);

    var prj3 = new Abc();
    prj3.id = 103;
    prj3.courselist = new List<xyz>();
    prj3.courselist.Add(new xyz { course = "imr101" });
    prj3.courselist.Add(new xyz { course = "imr102" });
    projects.Add(prj3);

    var grp1= projects.GroupBy(x => x.courselist).ToList();
    // var grp2 = grp1;
}

Expected result

[
    {
        courselist: [{course:imr101},{course:imr102}]
        ids: [101,103]
    },
    {
        courselist: [{course:imr201},{course:imr102}]
        ids: [102]
    }
]

CodePudding user response:

For grouping by collection you have to specify appropriate EqualityComparer for GroupBy function. Anyway even without that you can generate grouping Key:

var grp1 = projects.GroupBy(x => string.Join(",", x.courselist.Select(x => x.course))
    .Select(g => new 
    {
        courselist = g.First().courselist,
        ids = g.Select(x => x.Id).ToArray()
    })
    .ToList();

CodePudding user response:

It didn't work as expected because in your case, you need to provide an IEqualityComparer to your GroupBy method.
Here is one that I wrote for your case (order in course list will be ignored, if it's important, just remove the OrderBy clause):

internal class XyzListEqualityComparer : IEqualityComparer<List<xyz>>
{
    public bool Equals(List<xyz> x, List<xyz> y)
    {
        var xCourses = x
            .Select(subX => subX.course)
            .OrderBy(course => course);
        var yCourses = y
            .Select(subY => subY.course)
            .OrderBy(course => course);
        return xCourses.SequenceEqual(yCourses);
    }

    public int GetHashCode(List<xyz> obj)
    {
        int prime = 31;
        int result = 1;
        foreach(var item in obj.Select(course => course.course).OrderBy(course => course)) 
        {
            result = result * prime   item.GetHashCode();
        }
        return result;
    }
}

Then, you just need to write this for grouping using the IEqualityComparer:

var grp1 = projects.GroupBy(x => x.courselist, x => x, new XyzListEqualityComparer()).ToList();

If you do not want to use the IEqualityComparer, here is an algorithm that will do the trick and that will give you the exact output you are looking for (performance will not be the best and same thing regarding the order).

var groupedItems = projects
    .GroupBy(project => string.Join(",", project.courselist.Select(course => course.course).OrderBy(course => course)))
    .Select(groupedProject => new { courseList = groupedProject.First().courselist, ids = groupedProject.Select(project => project.id) });
  • Related