2009-01-20 7 views
21

Tengo una matriz de dobles y quiero el índice del valor más alto. Estas son las soluciones que he propuesto hasta ahora, pero creo que debe haber una solución más elegante. Ideas?¿Cómo obtengo el índice del valor más alto en una matriz usando LINQ?

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(); 
+1

Me pregunto si la gente realmente está buscando esto al buscar esta pregunta? 'System.Array.IndexOf (score, score.Max())' Acabo de ver un desarrollador de Unity usar el siguiente código LINQ para esta sencilla tarea y me quedé cara a cara. –

Respuesta

39

que sugieren escribir su propio método de extensión (editado para ser genérico con una restricción 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; 
} 

Tenga en cuenta que esto devuelve -1 si la secuencia está vacía.

Una palabra de las características:

  • Esto funciona con una secuencia que sólo se pueden enumerar una vez - esto a veces puede ser muy importante, y es generalmente una característica deseable de la OMI.
  • La complejidad de memoria es O (1) (a diferencia de O (n) para la clasificación)
  • La complejidad de tiempo de ejecución es O (n) (a diferencia de O (n log n) para la clasificación)

En cuanto a si esto "es LINQ" o no: si se hubiera incluido como uno de los operadores de consulta LINQ estándar, ¿lo considerarías como LINQ? ¿Se siente particularmente extraño o diferente de otros operadores LINQ? Si MS lo incluyera en .NET 4.0 como un nuevo operador, ¿sería LINQ?

EDIT: Si usted es muy, muy empeñado en el uso de LINQ (en lugar de conseguir una solución elegante), entonces aquí es una que todavía es O (n) y sólo evalúa la secuencia de una vez:

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

Es horrible, y no sugiero que lo uses para nada, pero funcionará.

+0

Esto no es LINQ Jon –

+0

Es Linq para objetos, Pascal. – Will

+2

@Pascal: ¿Cómo se define LINQ, exactamente? Para mí, una de las cosas buenas de LINQ es que puedes agregar tus propios operadores que funcionan sin problemas con los predefinidos. Edición por problemas de rendimiento. –

14
var scoreList = score.ToList(); 
int topIndex = 
    (
     from x 
     in score 
     orderby x 
     select scoreList.IndexOf(x) 
    ).Last(); 

Si score no era un arreglo esto no sería del todo malo ...

+0

Tengo que votar por Jon; es probablemente la mejor solución en general. Esta es la forma más sencilla de hacerlo sin escribir un método de extensión, aunque. – Will

+1

Tengo que votar Will. Me gusta la respuesta de Jon, pero parece estar más cerca de responder la pregunta. En última instancia, Guy nos dejará saber cuál es la mejor respuesta :) – wcm

+0

Will - Me encanta esta respuesta. Desde el punto de vista más puro, esta es probablemente la respuesta "correcta", pero sentí que la respuesta de Jon era la que yo quería. Gracias por tomarse el tiempo para responder la pregunta. – Guy

3

he tenido este problema hoy (para obtener el índice de una matriz de usuarios que tenía mayor edad), y lo hice de esta manera:

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

Fue en clase C#, por lo que su solución de novato, me 'Estoy seguro de que son mejores :)

+4

te das cuenta de que cada vez que comparas tu.Edad con los usuarios.Max (x => x.Age), ¿estás haciendo otra otra N viaje por encima de IEnumerable? Haciéndolo: O (n^2) complejidad. –

32

Meh, ¿por qué hacerlo demasiado complicado? Esta es la forma más simple.

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

Sí, se puede hacer un método de extensión para usar menos memoria, pero a menos que usted está tratando con grandes arreglos, se quiere no notar la diferencia.

+1

En mi libro esta debería ser la respuesta aceptada: ¿por qué una docena de líneas cuando una hará? – TaW

+2

... y el más lento también ... .Acerca de _nunca_ aviso: todos notamos que Windows es lento a pesar del hecho de que "no está tratando con arreglos enormes" –

+0

Pero esta no es la más lenta de todas las soluciones publicadas aquí . Además, ¿podría explicar lo que quiere decir con "Windows es lento"? – PJ7

0

La peor complejidad posible de esto es O (2N) ~ = O (N), pero necesita enumerar la colección dos veces.

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

Si quieres algo que parece LINQy, en la que es puramente funcional, entonces la respuesta Jon Skeets' arriba pueden ser reformuladas como:

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

Esto tiene la misma complejidad computacional como la otra respuesta, pero es más derrochador de memoria, creando una respuesta intermedia para cada elemento del enumerable.

1

Esta no es la única solución basada en agregados, pero esta es realmente una solución de una sola línea.

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=-1.0,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); 
+0

Tienes razón. No sabía que select tiene una versión sobrecargada que tiene un índice para lambda como parámetro, hasta que vi su respuesta. – zsf222

Cuestiones relacionadas