How to use LINQ to select objects with minimum or maximum attribute values

I have a Person object with the Nullable DateOfBirth attribute.Is there a way to use LINQ to query the earliest/smallest DataOfBirth value in the Person object list.

This is where I started:

var firstBornDate = People.Min(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue));

Empty DateOfBirth values are set to DateTime.MaxValue so that they are excluded from Min consideration (assuming at least one has a specified DOB).

But for me, all I have to do is set the first BornDate to the DateTime value.What I want is a Person object that matches this.Do I need to write a second query like this:

var firstBorn = People.Single(p=> (p.DateOfBirth ?? DateTime.MaxValue) == firstBornDate);

Or is there a more streamlined approach?

#1st floor

public class Foo {
    public int bar;
    public int stuff;
};

void Main()
{
    List<Foo> fooList = new List<Foo>(){
    new Foo(){bar=1,stuff=2},
    new Foo(){bar=3,stuff=4},
    new Foo(){bar=2,stuff=3}};

    Foo result = fooList.Aggregate((u,v) => u.bar < v.bar ? u: v);
    result.Dump();
}

#2nd floor

Solutions without additional packaging:

var min = lst.OrderBy(i => i.StartDate).FirstOrDefault();
var max = lst.OrderBy(i => i.StartDate).LastOrDefault();

You can also wrap it in an extension:

public static class LinqExtensions
{
    public static T MinBy<T, TProp>(this IEnumerable<T> source, Func<T, TProp> propSelector)
    {
        return source.OrderBy(propSelector).FirstOrDefault();
    }

    public static T MaxBy<T, TProp>(this IEnumerable<T> source, Func<T, TProp> propSelector)
    {
        return source.OrderBy(propSelector).LastOrDefault();
    }
}

Under these circumstances:

var min = lst.MinBy(i => i.StartDate);
var max = lst.MaxBy(i => i.StartDate);

By the way... O (n ^ 2) is not the best solution.Paul Betts offers a better solution than me.But I'm still the LINQ solution, which is simpler and shorter than the other solutions here.

#3rd floor

I've been looking for something similar, and it's best not to use libraries or sort the entire list.Ultimately, my solution is similar to the problem itself, just a little simplified.

var firstBorn = People.FirstOrDefault(p => p.DateOfBirth == People.Min(p2 => p2.DateOfBirth));

#4th floor

Below is a more general solution.It basically does the same thing (in O(N) order), but on any IEnumberable type, it can be mixed with a type where the property selector can return null.

public static class LinqExtensions
{
    public static T MinBy<T>(this IEnumerable<T> source, Func<T, IComparable> selector)
    {
        if (source == null)
        {
            throw new ArgumentNullException(nameof(source));
        }
        if (selector == null)
        {
            throw new ArgumentNullException(nameof(selector));
        }
        return source.Aggregate((min, cur) =>
        {
            if (min == null)
            {
                return cur;
            }
            var minComparer = selector(min);
            if (minComparer == null)
            {
                return cur;
            }
            var curComparer = selector(cur);
            if (curComparer == null)
            {
                return min;
            }
            return minComparer.CompareTo(curComparer) > 0 ? cur : min;
        });
    }
}

Test:

var nullableInts = new int?[] {5, null, 1, 4, 0, 3, null, 1};
Assert.AreEqual(0, nullableInts.MinBy(i => i));//should pass

#5th floor

Therefore, you want to use ArgMin or ArgMax.C# does not have built-in API s for these.

I've been looking for a clean and effective way (O [n]) to do this.I think I found one:

The general form of this pattern is:

var min = data.Select(x => (key(x), x)).Min().Item2;
                            ^           ^       ^
              the sorting key           |       take the associated original item
                                Min by key(.)

In particular, use the example from the original question:

For support Tuple of values C# 7.0 and later:

var youngest = people.Select(p => (p.DateOfBirth, p)).Min().Item2;

For C# versions prior to 7.0, you can use Anonymous Type :

var youngest = people.Select(p => new { ppl = p; age = p.DateOfBirth }).Min().ppl;

This works because both value tuples and anonymous types have smart default comparers: for (x1, y1) and (x2, y2), it first compares X1 to x2, then Y1 to y2.This is why built-in.Min can be used on these types.

And since both anonymous types and value tuples are value types, they should be very effective.

Be careful

In the ArgMin implementation above, for simplicity, I assume that the DateOfBirth type is DateTime.The original problem required that entries with an empty DateOfBirth field be excluded:

Empty DateOfBirth values are set to DateTime.MaxValue so that they are excluded from Min consideration (assuming at least one has a specified DOB).

This can be achieved by pre-filtering

people.Where(p => p.DateOfBirth.HasValue)

Therefore, ArgMax is the problem with implementing ArgMin or ArgMax.

Note 2

The above method has a warning that when there are two instances with the same minimum value, the Min() implementation will try to tie those instances as a winner.However, if the class of the instance does not implement IComparable, a runtime error is raised:

At least one object must implement IComparable

Fortunately, this can still be solved very cleanly.The idea is to associate an obvious "ID" with each entry that serves as a clear winner.We can use incremental IDs for each entry.Still take population age as an example:

var youngest = Enumerable.Range(0, int.MaxValue)
               .Zip(people, (idx, ppl) => (ppl.DateOfBirth, idx, ppl)).Min().Item3;

Tags: Attribute

Posted on Tue, 28 Jan 2020 19:16:18 -0800 by khalidorama