Home > Blockchain >  How can I use LINQ to get the instance with the highest value of a property in a child list?
How can I use LINQ to get the instance with the highest value of a property in a child list?

Time:03-03

I have the following class structure:

public class FirstClass
{
    public string EmployeeNo { get; set; }

    public List<SecondClass> SecondClass { get; set; }
}

public class SecondClass
{
    public int Count { get; set; }
}

I am using this code:

var result = FirstClass.Where(fc => fc.SecondClass.Aggregate((item1, item2) => item1.Count > item2.Count ? item1 : item2));

However, it results in this compiler error:

Cannot implicitly convert type of "SecondClass" into bool

How can I get the FirstClass object containing the SecondClass object with the highest value of the SecondClass.Count property? If multiple FirstClass objects have the same highest value it should return the first such object.

CodePudding user response:

You can select the count of each item in SecondClass and use Max to find the maximum value. Then you select this value of each item in FirstClass and use Max again:

int highestCount = input.Select(x => x.SecondClass.Select(y => y.Count).Max()).Max();

If you want to find the item with the highest count, you can replace the first Select by OrderByDescending and the second Max by FirstOrDefault:

var itemWithHighestCount = input.OrderByDescending(x => x.SecondClass.Select(y => y.Count).Max()).FirstOrDefault();

Online demo: https://dotnetfiddle.net/0rWARC

CodePudding user response:

Getting the maximum value of SecondClass.Count for a given FirstClass instance is straightforward...

int maxSecondClassCount = first.SecondClass.Max(second => second.Count);

...although note that Max() will throw an InvalidOperationException if first.SecondClass is empty. You can then use...

  • ...the MaxBy() method of .NET 6 to get the FirstClass instance with that maximum SecondClass.Count value...
    FirstClass firstClassWithMaxSecondClassCount = firstClassCollection.MaxBy(first => first.SecondClass.Max(second => second.Count));
    
    The MoreLINQ library also provides a MaxBy extension method.
  • ...the Aggregate() method of all .NET (Framework/Core) versions supporting LINQ to enumerate your FirstClass instances, keep track of the instance with the maximum value for SecondClass.Count, and return that FirstClass instance at the end...
    FirstClass firstClassWithMaxSecondClassCount = firstClassCollection.Aggregate(
        (element: default(FirstClass), count: 0), // The initial value of currentMax
        (currentMax, currentElement) => {
            int currentElementMaxCount = currentElement.SecondClass.Max(second => second.Count);
    
            return currentElementMaxCount > currentMax.count
                ? (element: currentElement, count: currentElementMaxCount)
                : currentMax;
        },
        currentMax => currentMax.element          // The return value given the final value of currentMax
    );
    
    That uses a tuple to store the maximum value of SecondClass.Count alongside the containing FirstClass instance so that value doesn't have to be recalculated in each invocation. Otherwise, this less complex but also less performant code could be used...
    FirstClass firstClassWithMaxSecondClassCount = firstClassCollection.Aggregate(
        (maxElement, currentElement) => 
            currentElement.SecondClass.Max(second => second.Count) > maxElement.SecondClass.Max(second => second.Count)
                ? currentElement : maxElement
    );
    
    The advantage of using Aggregate() instead of sorting and taking the element at one end of the result is that Aggregate() only needs to store one other value — the accumulator (currentMax/maxElement above) — as it enumerates the source sequence whereas sorting will require buffering the entire sequence to that point.
  • Related