Is there a nice way to add unique items from inner collections using LINQ method syntax

Posted on

Problem

Code

List<Element> elements = GetElementsList();

ISet<string> usedInnerElements = new HashSet<string>();
foreach (Element element in elements)
{
    foreach (InnerElement innerElement in element.InnerCollection.InnerElements)
    {
        usedInnerElements.Add(innerElement.SomeValue);
    }
}

class Element 
{
    public InnerCollection InnerCollection { get; set; }
}

class InnerCollection
{
    public List<InnerElement> InnerElements { get; set; }
}

class InnerElement
{
    public string SomeValue { get; set; }
}

This code scan inner collections of some other collection and save only unique values.

Question

Is there any way to present it in more fluent way using LINQ method syntax?

Solution

If we can assume that none of the values can be null then the simplest solution I can think of:

ISet<string> usedInnerElements = elements
    .SelectMany(coll => coll.InnerCollection.InnerElements
        .Select(elem => elem.SomeValue))
    .Distinct()
    .ToHashSet();
  1. With SelectMany we are reducing the dimensions (flattening) from IEnumerable<IEnumerable<string>> to IEnumerable<string>
  2. With Select we retrieve the string value from its wrapper class
  3. With Distinct we are making sure that we are getting rid of from all duplicates
  4. With ToHashSet we simply convert the IEnumerable<string> to ISet<string>

Test Input:

var elements = new List<Element>
{
    new Element
    {
        InnerCollection = new InnerCollection
        {
            InnerElements = new List<InnerElement>
            {
                new InnerElement { SomeValue = "A" },
                new InnerElement { SomeValue = "C" },
                new InnerElement { SomeValue = "E" },
            }
        }
    },
    new Element
    {
        InnerCollection = new InnerCollection
        {
            InnerElements = new List<InnerElement>
            {
                new InnerElement { SomeValue = "E" },
                new InnerElement { SomeValue = "A" },
                new InnerElement { SomeValue = "B" },
            }
        }
    }
};
...
Console.WriteLine(string.Join(",", usedInnerElements));

Test output:

A,C,E,B

If you have to deal with null values then the query might look like this:

ISet<string> usedInnerElements = elements
    .Where(coll => coll != null
        && coll.InnerCollection != null
        && coll.InnerCollection.InnerElements != null)
    .SelectMany(coll => coll.InnerCollection.InnerElements
        .Where(elem => elem != null)
        .Select(elem => elem.SomeValue))
    .Distinct()
    .ToHashSet();

Test input

var elements = new List<Element>
{
    null,
    new Element
    {
        InnerCollection = null
    },
    new Element
    {
        InnerCollection = new InnerCollection
        {
            InnerElements = null
        }
    },
    new Element
    {
        InnerCollection = new InnerCollection
        {
            InnerElements = new List<InnerElement>
            {
                new InnerElement { SomeValue = "E" },
                new InnerElement(),
                null
            }
        }
    }
};

Test output

E,

Leave a Reply

Your email address will not be published. Required fields are marked *