Lee Grissom
Lee Grissom

Reputation: 9953

How to join two lists?

(Full code available at: https://dotnetfiddle.net/tdKNgH)

I have two lists that are associated by ParentName and I would like to join them in a specific way.

class Parent
{
    public string ParentName { get; set; }
    public IEnumerable<string> ChildNames { get; set; }
}

class Child
{
    public string ParentName { get; set; }
    public string ChildName { get; set; }
}

var parents = new List<Parent>()
{
    new Parent() {ParentName = "Lee"},
    new Parent() {ParentName = "Bob"},
    new Parent() {ParentName = "Tom"}
};

var children = new List<Child>()
{
    new Child() {ParentName = "Lee", ChildName = "A"},
    new Child() {ParentName = "Tom", ChildName = "B"},
    new Child() {ParentName = "Tom", ChildName = "C"}
};

I'm using a foreach loop to join, and it works, but is there a more succinct way to do it?

foreach (var parent in parents)
{
    var p = parent; // to avoid foreach closure side-effects
    p.ChildNames = children.Where(c => c.ParentName == p.ParentName)
                           .Select(c => c.ChildName);
}

Here's what the resulting parents list would look like:

Parent Children
------ --------
Lee    A 
Bob    (empty) 
Tom    B,C

Upvotes: 4

Views: 256

Answers (5)

brz
brz

Reputation: 6016

You can use ToLookup for best performance with a little memory penalty:

 var clu = children.ToLookup(x => x.ParentName, x => x.ChildName);
 parents.ForEach(p => p.ChildNames = clu[p.ParentName]);

Upvotes: 1

ebb
ebb

Reputation: 9377

Given that LINQ is based on functional principles, side effects are generally a big no-no (and also the reason for why there's no foreach method).

I therefore suggest the following solution:

var parents = new List<Parent>()
{
    new Parent() { ParentName = "Lee" },
    new Parent() { ParentName = "Bob" },
    new Parent() { ParentName = "Tom" }
};

var children = new List<Child>()
{
    new Child() { ParentName = "Lee", ChildName = "A" },
    new Child() { ParentName = "Tom", ChildName = "B" },
    new Child() { ParentName = "Tom", ChildName = "C" }
};

var parentsWithChildren = parents.Select(x => new Parent 
{ 
    ParentName = x.ParentName, 
    ChildNames = children
        .Where(c => c.ParentName == x.ParentName)
        .Select(c => c.ChildName) 
});

foreach (var parent in parentsWithChildren)
{
    var childNamesConcentrated = string.Join(",", parent.ChildNames);

    var childNames = string.IsNullOrWhiteSpace(childNamesConcentrated) 
        ? "(empty)" : childNamesConcentrated;

    Console.WriteLine("Parent = {0}, Children = {1}", parent.ParentName, childNames);
}

The solution above, do not modify the Parent objects of the collection parents by setting their ChildNames. Instead it creates a new set of Parents with their respective ChildNames.

Upvotes: 1

Ken Hung
Ken Hung

Reputation: 782

Consider calling the name of a parent as Parent.Name instead of Parent.ParentName(a parent's parent?), Child has the same problem...

class Parent
{
    public string Name { get; set; }
    public IEnumerable<string> ChildrenNames { get; set; }
}

class Child
{
    public string ParentName { get; set; }
    public string Name { get; set; }
}

You can completely avoid foreach by creating parentNames array first:

var parentNames = new[] { "Lee", "Bob", "Tom" };
var allChildren = new List<Child>()
{
    new Child() {ParentName = "Lee", Name = "A"},
    new Child() {ParentName = "Tom", Name = "B"},
    new Child() {ParentName = "Tom", Name = "C"}
};

Such that the parents are constructed entirely by LINQ without any side-effects (no updates to any variables), and it should be very simple:

var parents =
    from parentName in parentNames
    join child in allChildren on parentName equals child.ParentName into children
    select new Parent { Name = parentName, ChildrenNames = children.Select(c => c.Name) };

Upvotes: 1

Matthew Haugen
Matthew Haugen

Reputation: 13286

You could do a group join. LINQ isn't meant to update, though. So I'm not sure whether this would actually get you anywhere useful.

IEnumerable<Parent> parents = ...;

var parentsWithChildren = parents.GroupJoin(children,
                                            c => c.ParentName,
                                            c => c.ParentName,
                                            (a, b) => new
                                                      {
                                                          Parent = a,
                                                          ChildNames = b.Select(x => x.ChildName)
                                                      });

foreach (var v in parentsWithChildren)
{
    v.Parent.ChildNames = v.ChildNames;
}

This would certainly help if all you were given were parent names and children, rather than full Parent objects, since then you could just group join that collection to the child names, and create instances of parents where I create an anonymous type ((a, b) => new { ... }). But since I'm assuming your Parent objects would realistically hold more than just a name and that this is just an example, this seems like your best bet.

Upvotes: 1

Roland Mai
Roland Mai

Reputation: 31077

You can add an extension method for enumerables:

public static void Each<T>(this IEnumerable<T> source, Action<T> action)
{
    if (action == null)
        return;
    foreach (T obj in source)
        action(obj);
}

And then do:

parents.Each(p => p.ChildNames = children.Where(c => c.ParentName == p.ParentName)
                                         .Select(c => c.ChildName));

Upvotes: 1

Related Questions