Home > Back-end >  Is there a way to find the closest number in an Array to another inputed number?
Is there a way to find the closest number in an Array to another inputed number?

Time:08-11

So I have a Visualstudio Forms where I have a NumericUpDown function that will allow users to input a 5 digit number such as 09456. And I need to be able to compare that number to an already existing array of similar 5 digit numbers, so essentially I need to get the inputted number and find the closest number to that.

 var numbers = new List<float> {89456f, 23467f, 86453f, };

// the list is way longer but you get the idea

        var target = numericUpDown.3 ;

        var closest = numbers.Select(n => new { n, (n - target) })
          .OrderBy(p => p.distance)
          .First().n;

But the first problem I encounter is that I cannot use a "-" operation on a float. Is there any way I can avoid that error and be able to still find the closest input?

CodePudding user response:

Anonymous type members need names, and you need to use the absolute value of the difference. eg

var numbers = new List<float> { 89456f, 23467f, 86453f, };
var target = 3;

var closest = numbers.Select(n => new { n, distance = Math.Abs(n - target) })
  .OrderBy(p => p.distance)
  .First().n;

CodePudding user response:

Well, apart from some issues in your sample(like no distance property on float) it should work:

int target = 55555;
float closest = numbers.OrderBy(f => Math.Abs(f - target)).First();

Demo: https://dotnetfiddle.net/gqS50L

CodePudding user response:

The answers that use OrderBy are correct, but have less than optimal performance. OrderBy is an O(N log N) operation, but why sort the whole collection when you only need the top element? By contrast, MinBy will give you the result in O(N) time:

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

CodePudding user response:

Apart from the compilation errors, using LINQ for this is very slow and time consuming. The entire list has to be scanned once to find the distance, then it needs to be sorted, which scans it all over again and caches the results before returning them in order.

Before .NET 6

A faster way would be to iterate only once, calculating the distance of the current item from the target, and keep track of which number is closest. That's how eg Min and Max work.

public static float? Closest(this IEnumerable<float> list, float target)
{
    float? closest=null;
    float bestDist=float.MaxValue;
    foreach(var n in list)
    {
        var dist=Math.Abs(n-target);
        if (dist<bestDist)
        {
            bestDist=dist;
            closest=n;
        }
    }
    return closest;
}

This will return the closest number in a single pass.

var numbers = new List<float> { 89456f, 23467f, 86453f, };
var closest=numbers.Closest(20000);
Console.WriteLine($"Closest is {closest}");
------------------
Closest is 23467

Using MoreLINQ and MinBy

The same can be done in a single line using the MinBy extension method from the MoreLINQ library:

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

Using MinBy

In .NET 6 and later, Enumerable.MinBy was added to the BCL:

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

The code is similar to the explicit loop once you look past the generic key selectors and comparers :

while (e.MoveNext())
{
    TSource nextValue = e.Current;
    TKey nextKey = keySelector(nextValue);
    if (nextKey != null && comparer.Compare(nextKey, key) < 0)
    {
        key = nextKey;
        value = nextValue;
    }
}
  •  Tags:  
  • c#
  • Related