LINQ获取最接近的值?

41

我有一个列表,MyStuff具有浮点类型的属性。

有一些对象的属性值为10、20、22、30。

我需要编写一个查询来查找最接近21的对象,在这种情况下,它会找到值为20和22的对象。然后我需要编写一个查询,找到最接近21但不超过21的对象,并返回值为20的对象。

对于这个问题,我不知道该从何处开始。能帮帮我吗?

谢谢。

更新 - 哇,这里有很多棒极了的回复。谢谢!我不知道应该遵循哪一个,所以我会尝试所有方法。让这个问题更(或者更少)有趣的一件事是,相同的查询将应用于LINQ-to-SQL实体,因此可能从MS Linq论坛中获得的答案最好?不确定。


22比21大,肯定会找到20吧? - cjk
是的,我是指20,抱歉搞错了。 - Snowy
4个回答

35
尝试按数字与21之间差值的绝对值排序,然后取第一个元素。
float closest = MyStuff
    .Select (n => new { n, distance = Math.Abs (n - 21) })
    .OrderBy (p => p.distance)
    .First().n;

或者根据@Yuriy Faktorovich的评论缩短它:

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

9
去除 "Select" 并将距离放入 "OrderBy" 中,可以缩短代码。 - Yuriy Faktorovich
1
如果 MyStuff 已经排序,那么可以在 O(n) 的时间内完成这个操作,而 OrderBy 则不能。如果你的列表已经排序并且不是很小(或者这段代码将在一个紧密的循环中执行),那么这是需要考虑的事情。 - Henry Mueller

26

这里有一个满足第二个查询的线性时间解决方案:

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

针对第一个查询,使用MoreLinqMinBy扩展将是最简单的方法:

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

您也可以使用标准的LINQ在线性时间内完成此操作,但需要对源进行两次遍历:

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

如果效率不是问题,你可以按顺序排序并选择第一个值,时间复杂度为O(n * log n),就像其他人已经回答的那样。


如果我们需要22怎么办? - Hassan Faghihi

10

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

4
List<float> numbers = new List<float>() { 10f, 20f, 22f, 30f };
float pivot = 21f;
var result = numbers.Where(x => x >= pivot).OrderBy(x => x).FirstOrDefault();

或者

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

这里是一个扩展方法:

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

使用方法:

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

1
你应该将 OrderBy 放在 Where 之后,这样它就不必对那么多元素进行排序。 - Gabe
@Gabe - 感谢您的建议。我已经修改了代码。 - Cheng Chen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接