LINQ查找比输入大/小的最接近数字

16

假设我有这个数字列表:

List<int> = new List<int>(){3,5,8,11,12,13,14,21}

假设我想要获取小于11的最接近数字,那么它将是8。 假设我想要获取大于13的最接近数字,那么它将是14。

列表中的数字不能重复并且始终有序。 我如何编写Linq来实现这一点?


7
为什么比 13 大的最接近的数是 21 而不是 14?我有什么地方理解错了吗? - Frédéric Hamidi
我的错,谢谢你告诉我。 - Sarawut Positwinyu
8个回答

22

使用 Linq,假设列表已经有序,我会这样做:

var l = new List<int>() { 3, 5, 8, 11, 12, 13, 14, 21 };
var lessThan11 = l.TakeWhile(p => p < 11).Last();
var greaterThan13 = l.SkipWhile(p => p <= 13).First();

编辑:

鉴于本答案收到了负面反馈,并为了那些可能会看到本答案并在接受时不再查看其他选项的人们,我研究了关于BinarySearch的其他评论,并决定在此添加第二个选项(稍作修改)。

这是其他地方介绍的不充分的方法:

var l = new List<int>() { 3, 5, 8, 11, 12, 13, 14, 21 };
var indexLessThan11 = ~l.BinarySearch(10) -1;
var value = l[indexLessThan11];

现在上面的代码没有考虑到值10可能实际上在列表中(在这种情况下不应该反转索引)!因此正确的方式是这样做:

var l = new List<int>() { 3, 5, 8, 11, 12, 13, 14, 21 };
var indexLessThan11 = l.BinarySearch(10);
if (indexLessThan11 < 0) // the value 10 wasn't found
{    
    indexLessThan11 = ~indexLessThan11;
    indexLessThan11 -= 1;
}
var value = l[indexLessThan11];

我想简单说明一下:

我只想指出:

l.BinarySearch(11) == 3
//and
l.BinarySearch(10) == -4;

1
这个解决方案非常清晰,意图很明确。我会把第二个写成 l.Reverse().TakeWhile(p => p > 13).Last(),以使其更加明显(并且与“小于”情况更加一致)。 - dbkk
实际上根据我所知,TakeWhile和SkipWhile都非常高效。顺便说一下,你总是可以再次发明轮子,但这有什么意义呢? - ub1k
1
@Vince @ub1k -- 对于效率问题,我们是有共识的。然而,在大多数情况下,可读性比效率更为重要(在微观层面上,而不是在架构决策方面)。如果在对应用程序进行分析后,您发现这段LINQ代码是瓶颈所在,那么它很容易被优化且范围明确。 - dbkk
1
这段程序代码应该是:var greaterThan13 = l.SkipWhile(p => p <= 13).First(); - Sarawut Positwinyu
1
我做了一个简单的测试(只是一个循环),结果发现TakeWhile()比二分查找慢4倍。如果你的应用程序每秒将被数千个请求服务,那么你应该考虑这一点,因为它可能会增加几秒钟的CPU时间。 - Razor
显示剩余3条评论

11

使用Array.BinarySearch - 无需使用LINQ或访问平均一半的元素即可找到目标。

还有很多适合你正在做的SortedXXX类,这些类内置高效的O(log N)搜索。


所以找到目标后,只需使用索引-1或索引+1,对吧,谢谢:D - Sarawut Positwinyu
多少 - 看看链接文档中的示例。它们涉及大型恐龙,因此您需要进行一些映射,但我没有看到任何可能明确回答您精确需求的内容 :P - Ruben Bartelink

6

您可以使用二分查找来实现。如果您正在搜索11,那么显然您将得到所需的索引。如果您搜索10并使用结果的按位补码,则会得到最接近的匹配项。

   List<int> list = new List<int>(){3,5,8,11,12,13,14,21};

   list.Sort();

   int index = list.BinarySearch(10);

   int found =  (~index)-1;

   Console.WriteLine (list[found]); // Outputs 8

同样的方法也适用于反向搜索。
int index = list.BinarySearch(15);

Console.WriteLine("Closest match : " + list[+~index]); // Outputs 21

二分查找也非常快速。


5

小于11的最接近的数字:

        int someNumber = 11;
        List<int> list = new List<int> { 3, 5, 8, 11, 12, 13, 14, 21 };

        var intermediate = from i in list
                     where i < someNumber
                     orderby i descending
                     select i;

        var result = intermediate.FirstOrDefault();

最接近13的大于它的数:

        int someNumber = 13;
        List<int> list = new List<int> { 3, 5, 8, 11, 12, 13, 14, 21 };

        var intermediate = from i in list
                     where i > someNumber
                     orderby i
                     select i;

        var result = intermediate.FirstOrDefault();

1

这是我的答案

List<int> myList = new List<int>() { 3, 5, 8, 11, 12, 13, 14, 21 };
    int n = 11;
    int? smallerNumberCloseToInput = (from n1 in myList
                                    where n1 < n
                                    orderby n1 descending
                                    select n1).First();

    int? largerNumberCloseToInput = (from n1 in myList
                                    where n1 > n
                                    orderby n1 ascending
                                    select n1).First();

谢谢伙计。它帮了我很多。 - Chandan Kumar

1
var list = new List<int> {14,2,13,11,5,8,21,12,3};
var tested = 11;

var closestGreater = list.OrderBy(n => n)
                         .FirstOrDefault(n => tested < n); // = 12

var closestLess = list.OrderByDescending(n => n)
                      .FirstOrDefault(n => tested > n); // = 8

if (closestGreater == 0)
    System.Diagnostics.Debug.WriteLine(
        string.Format("No number greater then {0} exists in the list", tested));

if (closestLess == 0)
    System.Diagnostics.Debug.WriteLine(
        string.Format("No number smaler then {0} exists in the list", tested));

1
这是我的方法,希望能对某些人有所帮助!
List<float> list = new List<float> { 4.0f, 5.0f, 6.0f, 10.0f, 4.5f,  4.0f, 5.0f, 6.0f, 10.0f, 4.5f, 4.0f, 5.0f, 6.0f, 10.0f };
float num = 4.7f;

float closestAbove = list.Aggregate((x , y) => (x < num ? y : y < num ? x : (Math.Abs(x - num)) < Math.Abs(y - num) ? x : y));
float closestBelow = list.Aggregate((x , y) => (x > num ? y : y > num ? x : (Math.Abs(x - num)) < Math.Abs(y - num) ? x : y));

Console.WriteLine(closestAbove);
Console.WriteLine(closestBelow);


这意味着您无需对列表进行排序。
来源:从这里改编:如何使用LINQ从List<int>中获取最接近的数字? 扩展代码
float closestAboveExplained = list.Aggregate((closestAbove , next) => {
    if(next < num){
        return closestAbove;
    }

    if(closestAbove < num){
        return next;
    }

    else{
        if(Math.Abs(closestAbove - num) < Math.Abs(next - num)){
            return closestAbove;
        }
    }
    return next;
});


0
您可以使用以下查询来实现此功能:

List<int> numbers = new List<int>() { 3, 5, 8, 11, 12, 13, 14, 21 };
List<int> output = (from n in numbers
                            where n > 13 // or whatever
                            orderby n ascending //or descending
                            select n).ToList();

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