How to group items by internal list

Viewed 156

I want to group a list of User by their according Group.
A user can be in multiple groups.

public class User
{
    public string Username { get; set; }
    public List<Group> Groups { get; set; }
}

The BusinessLogic returns me a List<User>.
Instead of Users have their Groups, I need to reform this list so Groups contains the Users.

Example:
Group 1:
- User 1
- User 2
Group 2:
- User 1
- User 3
Group 3:
- User 2
- User 3

I'm stuck on which extension method I need to use, so I'm not sure what to search for.
I would assume the GroupBy method but after that I'm lost :)

2 Answers

Use SelectMany to flatten the nested collections are retrieve tuples of user and it's groups. That way a use containing many groups will result in multiple record. Next group there records by the group and keep the user as the items in the group

var result = userList.SelectMany(user => user.Groups.Select(group => (user,group)))
                     .GroupBy(item => item.group, item => item.User);

In the current implementation, as the group by is performed on a Group object, that object should implement Equals and GetHashCode. If you don't want to do so then there exists an overload that also receives an IEqualityComparer. For more details see documentation.

Notice the use of Named Tuples. If you are using a C# prior to 7.0 then you can project an anonymous object instead or explicitly initialize a tuple and then also change the way you access properties - Item1 and Item2.

Solution proposed in the accepted answer does not work. My solution:

var result1 = from user in users
              from grp in user.Groups
              group user by grp into pivot
              select pivot;

Fixed version of Gilad code:

var result = users.SelectMany(user => user.Groups,(user, group)=>(user,group))
                          .GroupBy(item => item.group,item=>item.user);

Full code of mine and Gilad:

static void Main(string[] args)
{
    Group group1 = new Group("Group1");
    Group group2 = new Group("Group2");
    Group group3 = new Group("Group3");
    List<User> users = new List<User>(new User[] {
        new User() { Username = "User1", Groups = new List<Group>(new Group[] { group1,group2})  },
        new User() { Username = "User2", Groups = new List<Group>(new Group[] { group1,group3})  },
        new User() { Username = "User3", Groups = new List<Group>(new Group[] { group2,group3})  }
    });
    var result1 = from user in users
                 from grp in user.Groups
                 group user by grp into pivot
                 select pivot;
    foreach (var r in result1)
    {
        Console.WriteLine(r.Key.Name);
        foreach (var u in r)
            Console.WriteLine(u.Username);
    }

    Console.WriteLine();
    var result = users.SelectMany(user => user.Groups.Select(group => (user, group))
              .GroupBy(item => item.group,item=>item.user));
    foreach (var r in result)
    {
        Console.WriteLine(r.Key.Name);
        foreach (var u in r)
            Console.WriteLine(u.Username);
    }


    Console.ReadKey();
}

public class Group : IEquatable<Group>
{
    public Group(string name)
    {
        Name = name;
    }
    public string Name { get; set; }

    public bool Equals(Group other)
    {
        return other.Name == Name;
    }

    public override bool Equals(object obj)
    {
        return ((Group)obj).Name==Name;
    }

    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }
}
public class User
{
    public string Username { get; set; }
    public List<Group> Groups { get; set; }
}

Output (First comes my code, second Gilad's code output):

Group1
User1
User2
Group2
User1
User3
Group3
User2
User3

Group1
User1
Group2
User1
Group1
User2
Group3
User2
Group2
User3
Group3
User3
Related