2010-11-17 7 views
15

In Anbetracht dessen, dass dies eine sehr grundlegende Aufgabe ist, konnte ich mir keinen angemessen einfachen Weg vorstellen, dies zu tun. Wie würden Sie den Index des niedrigsten Wertes in einem int-Array erhalten? Die Verwendung von Linq/MoreLinq ist möglich. Ich konnte bisher keinen vernünftigen Einzeiler finden.Wie erhalten Sie den Index des niedrigsten Wertes in einem int-Array?

+2

Kann es im Array doppelte Nummern geben, und in welchem ​​Fall soll der Index angezeigt werden, wenn zwei der niedrigsten Werte vorhanden sind? ? – Paddy

+0

@Paddy Ja Duplikate sind möglich. Irgendwelche von diesen sind gut, zurückgebracht zu werden, obwohl ein (irgendein) beständiges Verhalten geschätzt würde (z. B. immer das letzte). – mafu

Antwort

17

Da Sie erwähnen MoreLinq, wie etwa:

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; 

Eine weitere Alternative:

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

Sie könnten natürlich Ihre eigene Neben-Methode schreiben:

// 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; 
} 

Mit einiger Mühe Sie können dies auf einen beliebigen Typ verallgemeinern, indem Sie eine IComparer<T> akzeptieren, standardmäßig Comparer<T>.Default.

+0

Warum haben Sie foreach in der Erweiterungsmethode gewählt? – mafu

+0

Im Allgemeinen kann auf Sequenzen nicht per Index zugegriffen werden. Die Verwendung einer 'foreach' ist der normale Weg, um eine beliebige Sequenz zu iterieren. Sie * könnten * den Enumerator bekommen und dann eine 'for'-Schleife benutzen, aber das würde ziemlich unordentlich aussehen. Wenn Sie den Enumerator wirklich brauchen, ist eine while-Schleife viel häufiger. In diesem Fall ist beides nicht notwendig. – Ani

+0

Hoppla, ja, ich habe versehentlich an Source als ein Array gedacht. – mafu

4

Nicht sehr Speicher freundlich, aber ...

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

Sie können OrderBy/First auch durch MinBy ersetzen. – mafu

+1

@mafutrct: yep, wenn Sie MoreLinq haben, was Sie tun, aber ich nicht :) –

1

Es ist hässlich, aber es braucht nur einen einzigen Durchlauf durch die Sequenz und verwendet nur eingebaute in Rahmen Methoden:

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

Und, natürlich können Sie eine Universalerweiterungsmethode schreiben:

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; 
     } 
    } 
} 
8

LINQ ist wahrscheinlich nicht die beste Lösung f oder dieses Problem, aber hier ist eine andere Variante, die O (n) ist. Es sortiert nicht und durchquert das Array nur einmal.

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 

Update: direkt die ursprüngliche Frage zu beantworten, das ist, wie ich es tun würde:

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 

Nein, es nicht LINQ verwenden. Ja, es ist mehr als eine Zeile. Aber es ist wirklich einfach und sehr schnell. Machen Sie es zu einer winzigen Methode und rufen Sie es von überall in einer einzigen Zeile an: pos = FindMinIndex(arr);

+0

Dies sollte die Antwort sein. Viel schneller und einfacher! – RMalke

+0

@RMalke Wie viel schneller? – shoelzer

+0

Ich habe nicht gemessen, aber ich war Select ((x, i) => ...). OrderBy (...). Zuerst (...) und ruft es genau 15257190400. Ich war deutlich schneller – RMalke

Verwandte Themen