代码之家  ›  专栏  ›  技术社区  ›  slolife

如何使用LINQ选择具有最小或最大特性值的对象

  •  394
  • slolife  · 技术社区  · 16 年前

    我有一个Person对象,其DateOfBirth属性为空。是否有一种方法可以使用LINQ查询具有最早/最小DateOfBirth值的Person对象列表?

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

    Null DateOfBirth值被设置为DateTime.MaxValue,以便排除最小考虑因素(假设至少有一个具有指定的DOB)。

    但我所做的就是将firstBornDate设置为DateTime值。我想得到的是与之匹配的Person对象。我是否需要像这样编写第二个查询:

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

    11 回复  |  直到 4 年前
        1
  •  325
  •   Sheridan    7 年前
    People.Aggregate((curMin, x) => (curMin == null || (x.DateOfBirth ?? DateTime.MaxValue) <
        curMin.DateOfBirth ? x : curMin))
    
        2
  •  245
  •   AustinWBryan ravenspoint    4 年前

    不幸的是,没有一个内置的方法来实现这一点,但它很容易为您自己实现。以下是它的精髓:

    public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source,
        Func<TSource, TKey> selector)
    {
        return source.MinBy(selector, null);
    }
    
    public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source,
        Func<TSource, TKey> selector, IComparer<TKey> comparer)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (selector == null) throw new ArgumentNullException("selector");
        comparer ??= Comparer<TKey>.Default;
    
        using (var sourceIterator = source.GetEnumerator())
        {
            if (!sourceIterator.MoveNext())
            {
                throw new InvalidOperationException("Sequence contains no elements");
            }
            var min = sourceIterator.Current;
            var minKey = selector(min);
            while (sourceIterator.MoveNext())
            {
                var candidate = sourceIterator.Current;
                var candidateProjected = selector(candidate);
                if (comparer.Compare(candidateProjected, minKey) < 0)
                {
                    min = candidate;
                    minKey = candidateProjected;
                }
            }
            return min;
        }
    }
    

    用法示例:

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

    如果有多个元素,则使用最小值。

    或者,您可以使用我们提供的实现 MoreLINQ 在里面 MinBy.cs . (有一个对应的 MaxBy 当然可以。)

    通过软件包管理器控制台安装:

    PM>安装软件包morelinq

        3
  •  156
  •   Lucas    16 年前

    注:为了完整性,我加入了这个答案,因为OP没有提到数据源是什么,我们不应该做任何假设。

    可能会慢一点 因为它可能需要排序 全部的 中的项目 People ,具体取决于数据结构 是:

    var oldest = People.OrderBy(p => p.DateOfBirth ?? DateTime.MaxValue).First();
    

    更新:实际上我不应该称这个解决方案为“幼稚”,但是用户需要知道他在查询什么。此解决方案的“慢度”取决于底层数据。如果这是一个数组或 List<T> ,则LINQ to Objects别无选择,只能在选择第一项之前先对整个集合进行排序。在这种情况下,它将比建议的其他解决方案慢。但是,如果这是一个LINQtoSQL表 DateOfBirth 是索引列,则SQL Server将使用索引而不是对所有行进行排序。其他习俗 IEnumerable<T> 实现还可以使用索引(参见 i4o: Indexed LINQ ,或对象数据库 db4o Aggregate() MaxBy() / MinBy() 需要对整个集合迭代一次。事实上,linqto对象(理论上)可以在 OrderBy() SortedList<T> ,但据我所知,事实并非如此。

        4
  •  65
  •   Rune FS    14 年前
    People.OrderBy(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue)).First()
    

    我会成功的

        5
  •  56
  •   nt86 KFL    5 年前

    那么你是在要求 ArgMin ArgMax . C#没有针对这些问题的内置API。

    我一直在寻找一种干净、高效(及时)的方法来做到这一点。我想我找到了一个:

    这种模式的一般形式是:

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

    value tuple :

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

    对于7.0之前的C#版本, anonymous type 可以改为使用:

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

    它们之所以有效,是因为值元组和匿名类型都有合理的默认比较器:对于(x1,y1)和(x2,y2),它首先进行比较 x1 x2 然后 y1 vs y2 . 这就是为什么内置 .Min

    笔记

    在我上面 DateOfBirth 打字 DateTime 为了简单明了。原始问题要求排除带有null的条目 出生日期 字段:

    Null DateOfBirth值被设置为DateTime.MaxValue,以便排除最小考虑因素(假设至少有一个具有指定的DOB)。

    它可以通过预滤波来实现

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

    阿明 阿格马克斯

    附注2

    Min() IComparable ,则将引发运行时错误:

    必须至少有一个对象实现IComparable

    var youngest = Enumerable.Range(0, int.MaxValue)
                   .Zip(people, (idx, ppl) => (ppl.DateOfBirth, idx, ppl)).Min().Item3;
    
        6
  •  22
  •   Andrew_STOP_RU_WAR_IN_UA    6 年前

    无额外软件包的解决方案:

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

    您还可以将其包装为扩展:

    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();
        }
    }
    

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

    顺便说一句O(n^2)不是最好的解决方案。 保罗·贝茨 给了比我更肥的解决方案。但我的仍然是LINQ解决方案,它比这里的其他解决方案更简单、更简短。

        7
  •  9
  •   hongxu    4 年前

    .NET 6 Preview 4

    People.MinBy(p => p.DateOfBirth)

        8
  •  3
  •   JustDave    10 年前
    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();
    }
    
        9
  •  3
  •   david.pfx    6 年前

    聚合的使用非常简单(相当于其他语言中的折叠):

    var firstBorn = People.Aggregate((min, x) => x.DateOfBirth < min.DateOfBirth ? x : min);
    

    唯一的缺点是每个序列元素访问属性两次,这可能会很昂贵。这很难解决。

        10
  •  2
  •   Vijay Nirmal    4 年前

    在.NET6(预览版7)或更高版本中,有新的内置方法 Enumerable.MaxBy Enumerable.MinBy 为了实现这一点。

    var lastBorn = people.MaxBy(p => p.DateOfBirth);
    
    var firstBorn = people.MinBy(p => p.DateOfBirth);
    
        11
  •  1
  •   zafar    8 年前

    以下是更通用的解决方案。它本质上做了相同的事情(按O(N)顺序),但在任何IEnumberable类型上都可以,并且可以与属性选择器可以返回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;
            });
        }
    }
    

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

    尝试以下想法:

    var firstBornDate = People.GroupBy(p => p.DateOfBirth).Min(g => g.Key).FirstOrDefault();
    
        13
  •  1
  •   Fran Turkovic    4 年前

    您可以像SQL中的order by和limit/fetch only技巧一样进行操作。所以,您按出生日期升序排序,然后只取第一行。

    var query = from person in People
                where person.DateOfBirth!=null
                orderby person.DateOfBirth
                select person;
    var firstBorn = query.Take(1).toList();
    
        14
  •  0
  •   Matthew Flaschen    16 年前

    再次编辑:

    Min<(Of <(TSource, TResult>)>)(IEnumerable<(Of <(TSource>)>), Func<(Of <(TSource, TResult>)>)) 按您所说的返回结果类型。

    我想说一个可能的解决方案是实现IComparable并使用它 Min<(Of <(TSource>)>)(IEnumerable<(Of <(TSource>)>)) ,它确实从IEnumerable返回一个元素。当然,如果您不能修改元素,这对您没有帮助。我觉得MS的设计有点奇怪。

    当然,如果需要,您可以始终执行for循环,或者使用Jon Skeet提供的MoreLINQ实现。

        15
  •  0
  •   Евгений Орлов    5 年前

    另一个实现可以使用可为null的选择器键,对于引用类型的集合,如果找不到合适的元素,则返回null。 例如,这可能有助于处理数据库结果。

      public static class IEnumerableExtensions
      {
        /// <summary>
        /// Returns the element with the maximum value of a selector function.
        /// </summary>
        /// <typeparam name="TSource">The type of the elements of source.</typeparam>
        /// <typeparam name="TKey">The type of the key returned by keySelector.</typeparam>
        /// <param name="source">An IEnumerable collection values to determine the element with the maximum value of.</param>
        /// <param name="keySelector">A function to extract the key for each element.</param>
        /// <exception cref="System.ArgumentNullException">source or keySelector is null.</exception>
        /// <exception cref="System.InvalidOperationException">source contains no elements.</exception>
        /// <returns>The element in source with the maximum value of a selector function.</returns>
        public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) => MaxOrMinBy(source, keySelector, 1);
    
        /// <summary>
        /// Returns the element with the minimum value of a selector function.
        /// </summary>
        /// <typeparam name="TSource">The type of the elements of source.</typeparam>
        /// <typeparam name="TKey">The type of the key returned by keySelector.</typeparam>
        /// <param name="source">An IEnumerable collection values to determine the element with the minimum value of.</param>
        /// <param name="keySelector">A function to extract the key for each element.</param>
        /// <exception cref="System.ArgumentNullException">source or keySelector is null.</exception>
        /// <exception cref="System.InvalidOperationException">source contains no elements.</exception>
        /// <returns>The element in source with the minimum value of a selector function.</returns>
        public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) => MaxOrMinBy(source, keySelector, -1);
    
    
        private static TSource MaxOrMinBy<TSource, TKey>
          (IEnumerable<TSource> source, Func<TSource, TKey> keySelector, int sign)
        {
          if (source == null) throw new ArgumentNullException(nameof(source));
          if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));
          Comparer<TKey> comparer = Comparer<TKey>.Default;
          TKey value = default(TKey);
          TSource result = default(TSource);
    
          bool hasValue = false;
    
          foreach (TSource element in source)
          {
            TKey x = keySelector(element);
            if (x != null)
            {
              if (!hasValue)
              {
                value = x;
                result = element;
                hasValue = true;
              }
              else if (sign * comparer.Compare(x, value) > 0)
              {
                value = x;
                result = element;
              }
            }
          }
    
          if ((result != null) && !hasValue)
            throw new InvalidOperationException("The source sequence is empty");
    
          return result;
        }
      }
    
    

    public class A
    {
      public int? a;
      public A(int? a) { this.a = a; }
    }
    
    var b = a.MinBy(x => x.a);
    var c = a.MaxBy(x => x.a);
    
        16
  •  0
  •   tech-gayan    5 年前

    如果要选择具有最小或最大特性值的对象。另一种方法是使用实现IComparable。

    public struct Money : IComparable<Money>
    {
       public Money(decimal value) : this() { Value = value; }
       public decimal Value { get; private set; }
       public int CompareTo(Money other) { return Value.CompareTo(other.Value); }
    }
    

    最大的执行将是。

    var amounts = new List<Money> { new Money(20), new Money(10) };
    Money maxAmount = amounts.Max();
    

    Min的实现将非常简单。

    var amounts = new List<Money> { new Money(20), new Money(10) };
    Money maxAmount = amounts.Min();
    

    通过这种方式,可以比较任何对象,并在返回对象类型时获取最大值和最小值。

    希望这能帮助别人。

        17
  •  0
  •   fredm73    5 年前

    通过IEnumerable上的扩展函数返回对象和找到的最小值的一种方法。它采用一个Func,可以对集合中的对象执行任何操作:

    public static (double min, T obj) tMin<T>(this IEnumerable<T> ienum, 
                Func<T, double> aFunc)
            {
                var okNull = default(T);
                if (okNull != null)
                    throw new ApplicationException("object passed to Min not nullable");
    
                (double aMin, T okObj) best = (double.MaxValue, okNull);
                foreach (T obj in ienum)
                {
                    double q = aFunc(obj);
                    if (q < best.aMin)
                        best = (q, obj);
                }
                return (best);
            }
    

    (double okDist, Airport best) greatestPort = airPorts.tMin(x => x.dist(okLat, okLon));
    
        18
  •  0
  •   idbrii    4 年前

    var min = People.Min(p => p.DateOfBirth);
    var firstBorn = People.FirstOrDefault(p => p.DateOfBirth == min);
    
        19
  •  -1
  •   Moch Yusup    4 年前

    您可以像MoreLinq一样使用现有的linq扩展。但是如果您只需要这些方法,那么您可以在这里使用简单的代码:

    public static IEnumerable<T> MinBys<T>(this IEnumerable<T> collection, Func<T, IComparable> selector)
    {
        var dict = collection.GroupBy(selector).ToDictionary(g => g.Key);
        return dict[dict.Keys.Min()];
    }
    public static IEnumerable<T> MaxBys<T>(this IEnumerable<T> collection, Func<T, IComparable> selector)
    {
        var dict = collection.GroupBy(selector).ToDictionary(g => g.Key);
        return dict[dict.Keys.Max()];
    }
    
    推荐文章