如何获取int数组中最小值的索引?

22

考虑到这是一个非常基本的任务,我无法想到一个合适的简单方法来完成它。您如何获得int数组中最小值的索引?使用Linq/MoreLinq是可能的。到目前为止,我还没有找到一个合理的一行代码。


2
数组中是否可能存在重复数字?如果最小值有两个,您想显示哪个索引? - Paddy
@Paddy 是的,重复是可能的。任何一个都可以被返回,但是希望有(任何)一致的行为(例如,总是返回最后一个)。 - mafu
5个回答

31

既然你提到了 MoreLinq,那么如何考虑:

int[] array = ..

// Will throw if the array is empty.
// If there are duplicate minimum values, the one with the smaller
// index will be chosen.
int minIndex = array.AsSmartEnumerable()
                    .MinBy(entry => entry.Value)
                    .Index;

另一种选择:

// Will throw if the array is empty.
// Requires two passes over the array. 
int minIndex = Array.IndexOf(array, array.Min());

当然可以编写自己的扩展方法:

// Returns last index of the value that is the minimum.
public static int IndexOfMin(this IEnumerable<int> source)
{
   if(source == null)
     throw new ArgumentNullException("source");

   int minValue = int.MaxValue;
   int minIndex = -1;
   int index = -1;

   foreach(int num in source)
   {
      index++;

      if(num <= minValue)
      {
         minValue = num;
         minIndex = index;
      }
   }

   if(index == -1)
     throw new InvalidOperationException("Sequence was empty");

   return minIndex;
}

通过一些努力,您可以通过接受一个 IComparer<T>(默认为 Comparer<T>.Default)来将其推广到任何类型。


为什么您在扩展方法中选择了foreach而不是for? - mafu
通常情况下,序列不能通过索引进行访问。使用 foreach 是迭代任意序列的正常方式。你可以获取枚举器然后使用 for 循环,但那看起来会很混乱。如果你真的需要枚举器,那么 while 循环更为常见。在这种情况下,两者都不必要。 - Ani
哎呀,是的,我不小心把source想成了一个数组。 - mafu
1
@mafutrct:实际上,对于数组foreach是经过优化的。它应该会产生与等效的for循环相同的IL代码。 - Ani
另外,我相信CLR带来的优化是消除了对for(i=0;i<arr.Length;i++)a[i].Something();进行数组边界检查的需求,这与C#编译器为数组上的foreach生成的代码类似。 - Ani
显示剩余2条评论

17

LINQ可能不是解决这个问题的最佳方案,但下面是另一种O(n)的变体。它不进行排序,仅遍历数组一次。

var arr = new int[] { 3, 1, 0, 5 };
int pos = Enumerable.Range(0, arr.Length)
    .Aggregate((a, b) => (arr[a] < arr[b]) ? a : b); // returns 2

更新: 直接回答原问题,我会这样做:

var arr = new int[] { 3, 1, 0, 5 };
int pos = 0;
for (int i = 0; i < arr.Length; i++)
{
    if (arr[i] < arr[pos]) { pos = i; }
}
// pos == 2

不,它不使用LINQ。是的,它不止一行代码。但它非常简单且非常快速。将其制作成一个小方法,并从任何地方调用它,只需一行:pos = FindMinIndex(arr);


1
这应该是答案。更快,更简单! - RMalke
@RMalke 快了多少? - shoelzer
我没有测量,但我使用Select((x, i)=>...).OrderBy(...).First(...)并且确切地调用了15257190400次。它的速度明显更快。 - RMalke

7

虽然不太友好于内存,但是...

array.Select((n, i) => new { index = i, value = n })
     .OrderBy(item => item.value)
     .First().index

你也可以用MinBy替换OrderBy/First。 - mafu
2
@mafutrct:是的,如果你有MoreLinq,而你有,但我没有 :) - Alex Humphrey

4

虽然它看起来不太美观,但它只需要一次遍历序列,并且仅使用内置框架方法:

int index = yourArray.Select((x, i) => new { Val = x, Idx = i })
                     .Aggregate(new { Val = -1, Idx = -1 },
                                (a, x) => (x.Idx == 0 || x.Val < a.Val) ? x : a,
                                x => x.Idx);

当然,你可以编写一个通用的扩展方法:

当然,您可以编写一个通用的扩展方法:

int index = yourArray.MinIndex();

// ...

public static class EnumerableExtensions
{
    public static int MinIndex<T>(
        this IEnumerable<T> source, IComparer<T> comparer = null)
    {
        if (source == null)
            throw new ArgumentNullException("source");

        if (comparer == null)
            comparer = Comparer<T>.Default;

        using (var enumerator = source.GetEnumerator())
        {
            if (!enumerator.MoveNext())
                return -1;    // or maybe throw InvalidOperationException

            int minIndex = 0;
            T minValue = enumerator.Current;

            int index = 0;
            while (enumerator.MoveNext())
            {
                index++;
                if (comparer.Compare(enumerator.Current, minValue) < 0)
                {
                    minIndex = index;
                    minValue = enumerator.Current;
                }
            }
            return minIndex;
        }
    }
}

第一个可能在性能方面很糟糕 ;) - AFract

0
int[] data = new []{ 10, 2, 3, 4, 2 };
var index = data
  .Select((v, i) =>new {Index = i, Val = v})
  .FirstOrDefault(v => v.Val == data.Min()).Index; // 1

但要小心,因为如果数组为空,你将会得到一个异常


解释一下你的答案,伙计。 - Kevin Hernandez
1
我希望答案中的附加数据足以理解答案。 - kanisimoff
这段代码会多次遍历数值。对于5个数字来说还好,但对于5_000_000个数字来说就不太好了。每次执行 Select 和 FirstOrDefault 时都会迭代一次,而每次执行 FirstOrDefault 都需要遍历所有内容以查找最小值。 - XWIKO

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接