2010-11-17 7 views
15

Teniendo en cuenta que esta es una tarea muy básica, no se me ocurre una forma adecuada de hacerlo. ¿Cómo obtendría el índice del valor más bajo en una matriz int? Usar Linq/MoreLinq es posible. No pude encontrar una línea razonable hasta ahora.¿Cómo obtendría el índice del valor más bajo en una matriz int?

+2

¿Puede haber números duplicados en la matriz, y en cuyo caso, qué índice desea mostrar si hay dos de los números más bajos? ? – Paddy

+0

@Paddy Sí duplicados son posibles. Cualquiera de estos está bien para ser devuelto, aunque se apreciará un comportamiento (cualquiera) constante (por ejemplo, siempre el último). – mafu

Respuesta

17

Ya que mencionas MoreLinq, ¿qué tal:

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; 

Otra alternativa:

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

Se podría, por supuesto, escribir su propia extensión método:

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

Con un poco de esfuerzo , puede generalizar esto a cualquier tipo aceptando un IComparer<T>, por defecto a Comparer<T>.Default.

+0

¿Por qué eligió foreach en el método de extensión? – mafu

+0

En general, no se puede acceder a las secuencias por índice. Usar un 'foreach' es la forma normal de iterar una secuencia arbitraria. Usted * podría * obtener * el enumerador y luego usar un bucle 'for', pero eso se vería bastante desordenado. Si realmente necesita el enumerador, un bucle 'while' es mucho más común. En este caso, ninguno es necesario. – Ani

+0

Vaya, sí, accidentalmente estaba pensando en 'fuente' como una matriz. – mafu

4

No es muy amigable memoria, pero ...

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

También podría reemplazar el OrderBy/First con MinBy. – mafu

+1

@mafutrct: sí, si tienes MoreLinq, lo que haces, pero yo no :) –

1

Es feo, pero sólo necesita una sola pasada a través de la secuencia y sólo utiliza métodos marco incorporadas:

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

Y, por supuesto, puede escribir un método de extensión de propósito general:

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 probablemente no sea la mejor solución f o este problema, pero aquí hay otra variación que es O (n). No ordena y solo cruza la matriz una vez.

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 

Actualización: Respondiendo a la pregunta original directamente, esta es la forma en que lo haría:

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 

No, No usa LINQ. Sí, es más de una línea. Pero es realmente simple y realmente rápido. Conviértalo en un pequeño método pequeño y llámelo desde cualquier lugar en una sola línea: pos = FindMinIndex(arr);

+0

Esta debería ser la respuesta. ¡Mucho más rápido y más simple! – RMalke

+0

@RMalke ¿Cuánto más rápido? – shoelzer

+0

No he medido, pero estaba seleccionado ((x, i) => ...). OrderBy (...). Primero (...) y llamándolo por 15257190400 veces exactas. No era mucho más rápido. – RMalke

Cuestiones relacionadas