nicholas revill
nicholas revill

Reputation: 1

Is there a way to find the closest number in an Array to another inputed number?

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?

Upvotes: 0

Views: 71

Answers (4)

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131492

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

Upvotes: 0

Klitos Kyriacou
Klitos Kyriacou

Reputation: 11631

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

Upvotes: 1

Tim Schmelter
Tim Schmelter

Reputation: 460158

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

Upvotes: 1

David Browne - Microsoft
David Browne - Microsoft

Reputation: 89166

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;

Upvotes: 1

Related Questions