2010-09-16 15 views
28

Tengo una lista, MyStuff tiene una propiedad de Type Float.LINQ para obtener el valor más cercano?

Existen objetos con valores de propiedad de 10,20,22,30.

Necesito escribir una consulta que encuentre los objetos más cercanos a 21, en este caso encontraría el objeto 20 y 22. Entonces necesito escribir uno que encuentre que el objeto se cierra a 21 sin pasar, y devolvería el objeto con un valor de 20.

No tengo ni idea de dónde/cómo comenzar con este. ¿Ayuda?

Gracias.

Actualización - wow, hay muchas respuestas increíbles aquí. ¡Gracias! No sé cuál seguir, así que los probaré todos. Una cosa que puede hacer esto más (o menos) interesante es que la misma consulta tendrá que aplicarse a las entidades LINQ-to-SQL, por lo que posiblemente la respuesta obtenida de los foros de MS Linq funcionará mejor. No lo se

+0

Er, 22 tiene más de 21 ... seguramente encontraría 20? – cjk

+0

Sí, quise decir 20, lo siento por el error. – Snowy

Respuesta

18

he aquí una solución que satisfaga a la segunda consulta en tiempo lineal:

var pivot = 21f; 
var closestBelow = pivot - numbers.Where(n => n <= pivot) 
            .Min(n => pivot - n); 

(Editado de 'arriba' a 'abajo' después de la clarificación)

En cuanto a la primera consulta, sería más fácil de usar MoreLinq 's MinBy extensión:

var closest = numbers.MinBy(n => Math.Abs(pivot - n)); 

También es posible hacer en LINQ estándar en el tiempo lineal, pero con 2 pases de la fuente:

var minDistance = numbers.Min(n => Math.Abs(pivot - n)); 
var closest = numbers.First(n => Math.Abs(pivot - n) == minDistance); 

Si la eficiencia no es un problema, que podría ordenar la secuencia y el escoger el primer valor de O(n * log n) como ot ella ha publicado.

+0

¿qué tal si necesitamos 22 en su lugar? – deadManN

21

Trate ordenándolos por el valor absoluto de la diferencia entre el número y 21 y luego tomar el primer elemento:

float closest = MyStuff 
    .Select (n => new { n, distance = Math.Abs (n - 21) }) 
    .OrderBy (p => p.distance) 
    .First().n; 

o la forma corta de acuerdo con el comentario de @Yuriy Faktorovich:

float closest = MyStuff 
    .OrderBy(n => Math.Abs(n - 21)) 
    .First(); 
+5

Podría acortar eso quitando 'Seleccionar' y poniendo la distancia en' OrderBy' –

6

sobre la base de this post en los foros de Microsoft LINQ:

var numbers = new List<float> { 10f, 20f, 22f, 30f }; 
var target = 21f; 

//gets single number which is closest 
var closest = numbers.Select(n => new { n, distance = Math.Abs(n - target) }) 
    .OrderBy(p => p.distance) 
    .First().n; 

//get two closest 
var take = 2; 
var closests = numbers.Select(n => new { n, distance = Math.Abs(n - target) }) 
    .OrderBy(p => p.distance) 
    .Select(p => p.n) 
    .Take(take);  

//gets any that are within x of target 
var within = 1; 
var withins = numbers.Select(n => new { n, distance = Math.Abs(n - target) }) 
    .Where(p => p.distance <= within) 
    .Select(p => p.n); 
2
List<float> numbers = new List<float>() { 10f, 20f, 22f, 30f }; 
float pivot = 21f; 
var result = numbers.Where(x => x >= pivot).OrderBy(x => x).FirstOrDefault(); 

O

var result = (from n in numbers 
       where n>=pivot 
       orderby n 
       select n).FirstOrDefault(); 

y aquí viene un método de extensión:

public static T Closest<T,TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector, TKey pivot) where TKey : IComparable<TKey> 
{ 
    return source.Where(x => pivot.CompareTo(keySelector(x)) <= 0).OrderBy(keySelector).FirstOrDefault(); 
} 

Uso:

var result = numbers.Closest(n => n, pivot); 
+1

Debería poner 'Order By' después de' the 'Where' para que no tenga que ordenar tantos elementos. – Gabe

+0

@Gabe - Gracias por su sugerencia. He modificado el código. –

Cuestiones relacionadas