2010-09-16 22 views
28

Ho una lista, MyStuff ha una proprietà di tipo mobile.LINQ per ottenere il valore più vicino?

Ci sono oggetti con valori di proprietà di 10,20,22,30.

Ho bisogno di scrivere una query che trova gli oggetti più vicini a 21, in questo caso troverà l'oggetto 20 e 22. Quindi ho bisogno di scriverne uno che trovi l'oggetto chiuso a 21 senza andare oltre, e restituirebbe l'oggetto con un valore di 20.

Non ho idea di dove/come iniziare con questo. Aiuto?

Grazie.

Aggiornamento - wow ci sono così tante fantastiche risposte qui. Grazie! Non so quale seguire, quindi li proverò tutti. Una cosa che potrebbe rendere questo più (o meno) interessante è che la stessa query dovrà applicarsi alle entità LINQ-to-SQL, quindi probabilmente la risposta raccolta dai forum MS Linq funzionerà al meglio? Non lo so

+0

Er, 22 è sopra 21 .... sicuramente ne troveresti 20? – cjk

+0

Sì, intendevo 20, mi dispiace per il fallimento. – Snowy

risposta

18

Ecco una soluzione che soddisfi la seconda query in tempo lineare:

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

(a cura di 'al di sopra' a 'basso', dopo un chiarimento)

Per quanto riguarda la prima domanda, che sarebbe stato più facile da usare MoreLinq s' MinBy estensione:

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

E' anche possibile fare in LINQ standard tempo lineare, ma con 2 pass della fonte:

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

Se l'efficienza non è un problema, è possibile ordinare la sequenza e scegliere il primo valore O(n * log n) come ot i suoi hanno pubblicato.

+0

e se invece avessimo bisogno di 22? – deadManN

21

Prova ordinandoli per il valore assoluto della differenza tra il numero e 21 e poi prendere la prima voce:

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

o accorciare secondo al commento di @Yuriy Faktorovich:

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

È possibile accorciarlo rimuovendo 'Seleziona' e inserendo la distanza in' OrderBy' –

6

sulla base this post al forum di 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(); 

e qui viene un metodo di estensione:

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

utilizzati:

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

Si dovrebbe mettere il 'OrderBy' * dopo * il' Dove' in modo che non debba ordinare tutti gli elementi. – Gabe

+0

@Gabe - Grazie per il tuo suggerimento. Ho modificato il codice. –

Problemi correlati