Home > Blockchain >  C# Linq get maximum of a nested element
C# Linq get maximum of a nested element

Time:10-04

I have a class like this

public class ValueGroup
{
    public string Name { get; set; }
    public List<Value> Values { get; set; }
}

public class Value
{
    public int RealValue { get; set; }
    public bool IsFavourite { get; set;
}

And a list of some items

var valueList = new List<ValueGroup>
{
    new ValueGroup
    {
        Name = "1st Group",
        Values = new List<Value>
        {
            new Value
            {
                RealValue = 6,
                IsFavourite = false
            },
            new Value
            {
                RealValue = 2,
                IsFavourite = true
            },
            new Value
            {
                RealValue = 4,
                IsFavourite = false
            }
        }
    },
    new ValueGroup
    {
        Name = "2nd Group",
        Values = new List<Value>
        {
            new Value
            {
                RealValue = 7,
                IsFavourite = false
            },
            new Value
            {
                RealValue = 3,
                IsFavourite = true
            },
            new Value
            {
                RealValue = 8,
                IsFavourite = true
            }
        }
    },
    new ValueGroup
    {
        Name = "3rd Group",
        Values = new List<Value>
        {
            new Value
            {
                RealValue = 9,
                IsFavourite = false
            },
            new Value
            {
                RealValue = 1,
                IsFavourite = true
            },
            new Value
            {
                RealValue = 5,
                IsFavourite = false
            }
        }
    }
}

So, now I would like to select the value group, which has the highest RealValue paired with an IsFavourit == true in its nested ValueList. So in this case, I want to select the 2nd group (the 3rd one has a bigger value, but this is not an isFavourite). Is there a chance to realize this with LINQ? Thanks for comments!

CodePudding user response:

If you want to select the whole ValueGroup object, use MaxBy.

ValueGroup? largestValueGroup = valueList
    .Where(vg => vg.Values.Any(v => v.IsFavourite))
    .MaxBy(vg => vg.Values
        .Where(v => v.IsFavourite)
        .Max(v => v.RealValue));

Console.WriteLine(largestValueGroup?.Name);
// 2nd Group

How this works:

  1. The first Where only allows ValueGroups through that have at least one Values entry where IsFavourite is true.

  2. MaxBy starts to evaluate all the remaining groups. For each group:

    1. Filter out any Value which is not a favorite.

    2. Get the max of the resulting Value entries (remember, we're still inside of an individual group) and get the maximum value.

      Normally, you'd get a InvalidOperationException if you pass an empty sequence to Max() (such as if there were no Value entries that were favorites), but that's what the very first Where does (before the MaxBy). When you run Max, you know for certain that at least one Value entry is a favorite.

  3. The MaxBy now has evaluated each source ValueGroup into a number, and finds the maximum, and returns the matching source object.

    However, if MaxBy got an empty collection, such is the case when ALL ValueGroups did not contain a favorite, then MaxBy itself will return null.

CodePudding user response:

So basically, all you have to do, is for every ValueGroup extract the Highest Favorite RealValue while remembering the ValueGroup. Result: combinations of [Highest Favorite RealValue, ValueGroup]

From these combinations find the one with the Highest RealValue.

The problem with Max and MaxBy is that they don't work well with empty collections: what is the highest RealValue is all are not IsFavorite?

Therefore I'll use OrderBy and FirstOrDefault, so I'm always certain that I get a value, even if all are not IsFavorite

IEnumerable<ValueGroup> valueGroups = ...
ValueGroup valueGroupWithLargestFavoriteRealValue = valueGroups
    .Select(valueGroup => new
    {
        HighestFavoriteRealValue = valueGroup.Values
            .Where(value => value.IsFavorite)
            .Select(value => value.RealValue)
            .OrderByDescending(realValue => realValue)
            .FirstOrDefault(),
        ValueGroup = valueGroup,
    })
    .OrderByDescending(combination => combination.HighestFavoriteRealValue)
    .Select(combination => combination.ValueGroup)
    .FirstOrDefault();

In words: from every valueGroup in your input sequence of valueGroups make one combination element that has two properties:

  • HighestFavoriteValue
  • ValueGroup: the complete valueGroup

To calculate the HighesFavoriteValue of this valueGroup, take all Values of this valueGroup, and keep only those that are IsFavorite. From the remaining values take the RealValue. Result: a bunch of realValues that all has IsFavorite. Order this bunch in descending order and take the first or default one. If there were no IsFavorites you get zero, otherwise you have the Highest Favorite real value.

Order this sequence of combinations [HighestFavoriteValue, ValueGroup] by descending value of HighestFavoriteValue. From every combination in this sorted sequence select the ValueGroup. From the resulting sequence of ValueGroups take the first, or the default if there is no ValueGroup left.

  • Related