【问题标题】:How do I get the index of the highest value in an array using LINQ?如何使用 LINQ 获取数组中最大值的索引?
【发布时间】:2009-01-20 19:23:04
【问题描述】:

我有一个双精度数组,我想要最大值的索引。这些是我迄今为止提出的解决方案,但我认为必须有一个更优雅的解决方案。想法?

double[] score = new double[] { 12.2, 13.3, 5, 17.2, 2.2, 4.5 };
int topScoreIndex = score.Select((item, indx) => new {Item = item, Index = indx}).OrderByDescending(x => x.Item).Select(x => x.Index).First();

topScoreIndex = score.Select((item, indx) => new {Item = item, Index = indx}).OrderBy(x => x.Item).Select(x => x.Index).Last();

double maxVal = score.Max();
topScoreIndex = score.Select((item, indx) => new {Item = item, Index = indx}).Where(x => x.Item == maxVal).Select(x => x.Index).Single();

【问题讨论】:

  • 我想知道人们在搜索这个问题时是否真的在寻找这个? System.Array.IndexOf(score, score.Max()) 刚刚看到一个 Unity 开发人员使用下面的 LINQ 代码来完成这个简单的任务,我当时就在摸脸。

标签: c# linq


【解决方案1】:

嗯,为什么要让它过于复杂?这是最简单的方法。

var indexAtMax = scores.ToList().IndexOf(scores.Max());

是的,您可以创建一个扩展方法来使用更少的内存,但除非您处理的是巨大的数组,否则您将永远注意到其中的区别。

【讨论】:

  • 在我的书中,这应该是公认的答案——为什么要写十几行?
  • ...也是最慢的...。关于 never 通知:我们都注意到 Windows 很慢,尽管事实上“它 不是 处理巨大的数组”
  • 但这并不是这里发布的所有解决方案中最慢的。另外,您能否解释一下“Windows 很慢”是什么意思?
  • 我觉得自己很感兴趣,并用扩展方法测试了 PJ7 提案的性能。任务是在随机字符串列表(8 到 32 个字符长)中找到最大元素的索引(标准字符串比较)。列表的长度高达 10000000。结果在统计上是相同的,例如对于长度为 32 的 1e7 字符串,扩展方法需要 1400 ms,而 PJ7 需要 1530。(在单元测试、调试模式、Windows 10、Intel i7、2.8 GHz、RAM 32 GB 中使用 Stopwatch 进行测试)。
  • 另外说明:如果scores 为空,则完全按照上面引用的方式编写的代码行将抛出异常,如果数组为空,则会抛出InvalidOperationException。所以需要尝试/捕获。
【解决方案2】:

我建议编写自己的扩展方法(编辑为具有IComparable<T> 约束的通用方法。)

public static int MaxIndex<T>(this IEnumerable<T> sequence)
    where T : IComparable<T>
{
    int maxIndex = -1;
    T maxValue = default(T); // Immediately overwritten anyway

    int index = 0;
    foreach (T value in sequence)
    {
        if (value.CompareTo(maxValue) > 0 || maxIndex == -1)
        {
             maxIndex = index;
             maxValue = value;
        }
        index++;
    }
    return maxIndex;
}

请注意,如果序列为空,则返回 -1。

关于特性的一句话:

  • 这适用于只能枚举一次的序列 - 这有时非常重要,通常是 IMO 的理想功能。
  • 内存复杂度为 O(1)(相对于排序的 O(n))
  • 运行时复杂度为 O(n)(相对于排序的 O(n log n))

至于这是否“是 LINQ”:如果它已被列为标准 LINQ 查询运算符之一,您会将其视为 LINQ 吗?它是不是感觉特别陌生或与其他 LINQ 运算符不同?如果 MS 将其作为新的运算符包含在 .NET 4.0 中,会是 LINQ 吗?

编辑:如果您真的非常非常想使用 LINQ(而不是仅仅获得一个优雅的解决方案),那么这里仍然是 O(n) 并且只评估序列一次:

int maxIndex = -1;
int index=0;
double maxValue = 0;

int urgh = sequence.Select(value => {
    if (maxIndex == -1 || value > maxValue)
    {
        maxIndex = index;
        maxValue = value;
    }
    index++;
    return maxIndex;
 }).Last();

这很可怕,我不建议你使用它 - 但它会起作用。

【讨论】:

  • 这是 Linq to Objects,Pascal。
  • @Pascal:你是如何定义 LINQ 的?对我来说,LINQ 的优点之一是您可以添加自己的运算符,这些运算符可以与预定义的运算符一起顺利工作。针对性能问题进行编辑。
  • @Jon:知道了!我偏离了轨道。话虽如此。优雅的解决方案!
  • 这是一个很好的答案,乔恩 - 谢谢。我倾向于参考这种 LINQ 之类的扩展方法,但我猜如果归根结底,我会失去语义参数。
  • @JonSkeet 受到您的批评的启发,我只是将其修改为 ((value!= null && value.CompareTo(maxValue) > 0) || maxIndex == -1) 应该可以。
【解决方案3】:
var scoreList = score.ToList();
int topIndex =
    (
      from x
      in score
      orderby x
      select scoreList.IndexOf(x)
    ).Last();

如果score 不是一个数组,这还不错......

【讨论】:

  • 我必须投票给乔恩;它可能是整体上更好的解决方案。这是 linq-iest 的方式,无需编写扩展方法。
  • 我必须投票支持威尔。我喜欢乔恩的回答,但这似乎更接近于回答所提出的问题。最终,Guy 会告诉我们哪个答案是最好的 :)
  • 威尔 - 我喜欢这个答案。从最纯粹的角度来看,这可能是“正确”的答案,但我觉得乔恩的答案正是我想要的。感谢您抽出宝贵时间回答问题。
  • 对于 O(n) 的操作对列表( O(log(n)) )进行排序真的是一个很好的解决方案吗?
【解决方案4】:

这不是唯一的基于聚合的解决方案,但这实际上只是一个单行解决方案。

double[] score = new double[] { 12.2, 13.3, 5, 17.2, 2.2, 4.5 };

var max = score.Select((val,ix)=>new{val,ix})
               .Aggregate(new{val=Double.MinValue,ix=-1},(z,last)=>z.val>=last.val?z:last);

Console.WriteLine ("maximum value is {0}", max.val );
Console.WriteLine ("index of maximum value is {0}", max.ix );

【讨论】:

  • 你是对的。我不知道 select 有一个重载版本,它有一个 lambda 索引作为参数,直到我看到你的答案。
  • 如果您的列表包含负值,则不起作用。您可以使用val=Double.MinValuez.val&gt;=last.val 解决(因此,如果数组包含 MinValue,您将获得正确的索引)。
  • @idbrii 好主意,我添加了这些改进以处理更大范围的数字空间。
【解决方案5】:

试试这个完全是LINQ并且性能最好的:

var indexAtMax = scores.Select((x, i) => new { x, i })
            .Aggregate((a, a1) => a.x > a1.x ? a : a1).i;

【讨论】:

    【解决方案6】:

    我今天遇到了这个问题(获取年龄最高的用户数组中的索引),我这样做了:

    var position = users.TakeWhile(u => u.Age != users.Max(x=>x.Age)).Count();
    

    它在 C# 类上,所以它的菜鸟解决方案,我相信你的解决方案更好:)

    【讨论】:

    • 您确实意识到,每次将 u.Age 与 users.Max(x=>x.Age) 进行比较时,您正在对 IEnumerable 进行另一次 N 次旅行?实现:O(n^2) 复杂度。
    【解决方案7】:

    System.Linq.Enumerable.Select 带索引和System.Linq.Enumerable.Aggregate 将在一行中完成

    public static int IndexOfMax<TSource>(this IEnumerable<TSource> source)
        where TSource : IComparable<TSource> => source.Select((value, idx) => (value, idx))
        .Aggregate((aggr, next) => next.value.CompareTo(aggr.value) > 0 ? next : aggr).idx;
    

    【讨论】:

    • 欢迎来到 SO。请注意,这个问题是从 2009 年开始的。您提供的答案当时可能不可用。另外,请尽量避免仅使用代码的答案。虽然代码可能是问题的解决方案,但它可能不是每个人都能理解的。请添加一些解释。
    【解决方案8】:

    这个最糟糕的可能复杂度是 O(2N) ~= O(N),但它需要枚举集合两次。

     void Main()
    {
        IEnumerable<int> numbers = new int[] { 1, 2, 3, 4, 5 };
    
        int max = numbers.Max ();
        int index = -1;
        numbers.Any (number => { index++; return number == max;  });
    
        if(index != 4) {
            throw new Exception("The result should have been 4, but " + index + " was found.");
        }
    
        "Simple test successful.".Dump();
    }
    

    【讨论】:

      【解决方案9】:

      如果你想要一些看起来 LINQy 的东西,因为它纯粹是功能性的,那么 Jon Skeets 上面的回答可以改写为:

      public static int MaxIndex<T>(this IEnumerable<T> sequence) where T : IComparable<T>
          {
              return sequence.Aggregate(
                  new { maxIndex = -1, maxValue = default(T), thisIndex = 0 },
                  ((agg, value) => (value.CompareTo(agg.maxValue) > 0 || agg.maxIndex == -1) ?
                                   new {maxIndex = agg.thisIndex, maxValue = value, thisIndex = agg.thisIndex + 1} :
                                   new {maxIndex = agg.maxIndex, maxValue = agg.maxValue, thisIndex = agg.thisIndex + 1 })).
                  maxIndex;
          }
      

      这与另一个答案具有相同的计算复杂性,但更浪费内存,为可枚举的每个元素创建一个中间答案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-12-09
        • 1970-01-01
        • 1970-01-01
        • 2019-09-11
        • 2021-07-19
        • 2019-09-04
        • 2011-10-18
        相关资源
        最近更新 更多