Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find closest value in a list over 360 degrees

I have a long list of numbers that contains measured angles. The basic idea is that it looks something like this:

var list = new List<double>() {352.9, 354.9, 356.9, 359, 1, 3.1, 5.9};

I am looking for a way to obtain the nearest upper and lower value when I specify some value x. So for example if x = 354.6, I want the upper value to be x_up = 354.9 and the lower value to be x_low = 352.9. I though about using this method, but it does not taking into account that circle angles follow a modulo system.

When x = 0.2, I want x_up = 1 and x_low = 359.

Any ideas on how I can implement this?

like image 740
WaterDrop Avatar asked Sep 11 '25 14:09

WaterDrop


2 Answers

Please, note that floating point types (double, float) support remainder operation %; so you can put it as

  using System.Linq;

  ...

  List<double> list = new() {
    352.9, 354.9, 356.9, 359, 1, 3.1, 5.9
  };

  double x = 0.2;

  // 359
  var x_low = list.MaxBy(item => ((item - x) % 360 + 360) % 360);
  // 1
  var x_up = list.MinBy(item => ((item - x) % 360 + 360) % 360);

Edit: If you can't use MaxBy you can put good old foreach loop:

float x_low = -1;
float x_up = -1;
float x_max = -1;
float x_min = -1;

foreach (var item in list) {
  var value = ((item - x) % 360 + 360) % 360;

  if (x_up < 0 || value < x_min) {
    x_min = value;
    x_up = item;
  }

  if (x_low < 0 || value > x_max) {
    x_max = value;
    x_low = item;
  }
}
like image 78
Dmitry Bychenko Avatar answered Sep 14 '25 05:09

Dmitry Bychenko


I would highly recommend creating a Angle-type rather than using float/double to represent angles. This saves so much confusion when reading code since you do not have to worry about if it is in degrees or radians, and gives a nice way to gather various angle-related functionality. Make it a readonly struct reduce the overhead to nearly nothing.

This should allow you to make a function to normalize the angles:

private readonly double radians;
public Angle Normalized180_180{
  get{
     var Pi2 = PI * 2;
     var rad = radians;
     rad = (((rad % Pi2) + Pi2) % 360) - 180;
     return new Angle(rad);
  }
}

This should give you a angle guaranteed to be in the -180 to 180 interval, but you could adjust the code to use whatever interval you prefer.

Then you can take the difference, (overload operators to make this simple), normalize, and select the smallest negative and smallest positive value:

var nextSmallest = myAngles.MinBy(a => {
    var diff = (targetAngle - a).Normalized180_180.Radians;
    if(diff < 0)   return double.MaxValue;
    return diff;
};

Another approach would be to normalize and sort your values, and use BinarySearch to find the position of your target value in the list, but you need to fiddle around with the returned value to get the position, and probably also some separate checks to handle edge cases, like the beginning and end of the list.

like image 42
JonasH Avatar answered Sep 14 '25 04:09

JonasH