Min/Max functions that return an index in addition to result - giveaway

Edit: Full library of versatile methods for optimal search for the nearest or farthest of points (according to the selected distance method) supplied in an array or list (or directly specified in the arguments) can be found in Post #5 (below). Provided methods support Euclidean (everyday 3D), Manhattan (no diagonal movement), and Chebyshev (aka ‘chessboard’) distances, as well as custom specification.

Maybe you didn’t know that you wanted this in your life so badly.
Maybe you still don’t want it.

I do. For some reason it’s stupidly frequent.

You know, when you want to find the minimum value in an array, and then you get your answer “42”
URGHH, not what, where?!

Well, let’s just IndexOf right? HA :slight_smile: (software engineering joke)

I don’t know, maybe it’s just me, but I’ve written a dozen of little in-place variants of the same thing.

In the end I’ve settled down with Vector2 and Vector3 extensions IndexOfMin and IndexOfMax, but this isn’t a universal Min/Max solution that can run with an arbitrary-sized arrays or a series of arguments.

The call should be as simple as this

float minimum = Min(out int indexOfMin, 15.5f, 11f, 100f, 0.22f, 0.7f);
float maximum = Max(out int indexOfMax, 15.5f, 11f, 100f, 0.22f, 0.7f);

and ideally, it should do everything in one pass. (If you didn’t get it, a subsequent IndexOf is a total waste of electric power.)

Obviously, it still has to be O(n), there are no shortcuts. And I’m not intending this to be a replacement for the usual Min/Max functions. However, while we’re at it, let’s make something more universal out of it, for added value.

A general case of Min/Max should work on both floats and ints, for example. We could overload, all right. But then, why not implement something even more general, based on some arbitrary criterion, and turn Min/Max variants into special cases of that.

So, long story short, I give you BestOf

static public T BestOf<T>(int startIndex, int length, Func<T, T, bool> criterion, out int index, params T[] values) {
  if(criterion == null || values == null) throw new ArgumentNullException();
  if(startIndex < 0 || length <= 0) throw new IndexOutOfRangeException();
  if(startIndex + length > values.Length) throw new IndexOutOfRangeException();

  index = startIndex;

  int last = index + length - 1;
  int hlen = index + (length >> 1);

  for(int i = index; i < hlen; i++) {
    if(criterion(values[i], values[index])) index = i;
    if(criterion(values[last - i], values[index])) index = last - i;
  }

  if((length & 1) != 0 && criterion(values[hlen], values[index])) index = hlen;

  return values[index];
}

// overload without startIndex and length, for the more common usage
static public T BestOf<T>(Func<T, T, bool> criterion, out int index, params T[] values)
  => BestOf(0, values.Length, criterion, out index, values);

Now I can constrain Min/Max to comparable types, and get what I wanted in the first place

static public T Min<T>(out int index, params T[] values) where T : IComparable
  => BestOf((a, b) => a.CompareTo(b) < 0, out index, values);

static public T Max<T>(out int index, params T[] values) where T : IComparable
  => BestOf((a, b) => a.CompareTo(b) > 0, out index, values);

Now this is a very nice generic solution, and pretty fast with short arrays (I haven’t tested it with large ones, but it should perform predictably steady), however it is more than 14x slower than Mathf.Min/Max in relative terms.

This isn’t a huge problem for the general case, but with regular arrays of floats and ints in critical paths, it’s, well not exactly crawling, but shambling, and frankly I wouldn’t use it.

The actual source of the problem seems to lie in the actual frequency of criterion calls (btw, lambda is very fast compared to fixed methods, even if they’re written as aggressively-inlined expression bodies). If someone knows how to optimize this further, let me know.

[edit]
Nah, I’ve solved it. It should be constrained by IComparable
I’ve hit boxing/unboxing. Now it’s only 3x slower in the general case. Full code below.
[/edit]

In the meanwhile, if you’re concerned about performance, you can always make a fixed overload in the type of your choosing, and you’ll get it running 15% faster than Mathf.Min/Max in the best case*! And here I’m talking about 2 millions of short array scans (with up to 10 elements), shoulder to shoulder comparison. (I’m guessing that I’m bypassing the infamous “crossing the C++ threshold” that seems to be the case with all Mathf functions in general.)

  • when floats are fuzzy, and aren’t pure integers; integer variant is similar in performance.

Here’s a flattened-out float Min as an example

static public float Min(out int index, params float[] values) {
  if(values == null) throw new ArgumentNullException();
  if(values.Length == 0) throw new IndexOutOfRangeException();

  index = 0;

  int last = index + length - 1;
  int hlen = index + (length >> 1);

  for(int i = index; i < hlen; i++) {
    if(values[i] < values[index]) index = i;
    if(values[last - i] < values[index]) index = last - i;
  }

  if((length & 1) != 0 && values[hlen] < values[index]) index = hlen;

  return values[index];
}

Btw, it’s worth noting that due to the symmetrical design of the sweep, the returned index may not always be left-to-right in case of ambiguous values. For example, an array { 4, 3, 5, 3, 5, 3 } will return 5 as a winning index of minimum, instead of 1, and 4 as a winning index of maximum, instead of 2. Therefore, the exact index shouldn’t be considered as having an ascending priority. In my general case, this is fine, and actually diminishes a common left-to-right bias, but you can change the actual loop if you find this annoying.

Full code (See next post)

I now have an entire zoo of these things. I really need them a lot :slight_smile:

I squeezed out the maximum in terms of performance, and also included inlined statically-typed variants with 2 and 3 int/float arguments, and MinMax combo functions that do both things in one pass (these return value tuples and have two out parameters; a special case mandates a special usage). All methods got a variant to handle IList as well (this works with both Lists and arrays, although params methods already handle arrays as well, this is just for compatibility).

Obviously all of this became a burden on my original static class, so I’ve made a dedicated class to host them all.

Performance notice:
Generic methods run about 3x slower on my computer, the statically typed ones are comparable to Unity’s Mathf.Min/Max or perform 15-20% better.

Rookie information:
Normally you don’t want to repeat yourself like I do in this code, however this is sometimes unavoidable in pursuit of performance, and I needed performance because this is likely to be used on critical paths, such as algorithms that compare a lot of data, try to approximate something in microseconds etc. Typically such algorithms need to rely on fast but dedicated functions so that they can stay as compact and readable as possible.

Use cases:
A typical usage scenario would be to have an array of points, from which it is possible to build an array of distances/squared distances (in relation to something). Finding the nearest point in a single pass becomes trivial (and very clean), because we also get to reuse the index for the original point array.

I.e.

var points = new Vector3[] { ... };
var sqrDistances = new float[points.Length];
for(int i = 0; i < points.Length; i++) sqrDistances[i] = (points[i] - somePoint).sqrMagnitude;
var leastSqrDistance = Comparator.Min(out minIndex, sqrDistances);
Debug.Log($"Closest point is {points[minIndex]} at a distance of {Mathf.Sqrt(leastSqrDistance)}");

Full code

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace CommonExtensions {

  static public class Comparator {

    /// <summary>
    /// Inlined Min with 2 int arguments.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static public int Min(out int index, int a, int b) {
      if(a <= b) { index = 0; return a; }
      index = 1; return b;
    }

    /// <summary>
    /// Inlined Max with 2 int arguments.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static public int Max(out int index, int a, int b) {
      if(a >= b) { index = 0; return a; }
      index = 1; return b;
    }

    /// <summary>
    /// Inlined Min with 2 float arguments.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static public float Min(out int index, float a, float b) {
      if(a <= b) { index = 0; return a; }
      index = 1; return b;
    }

    /// <summary>
    /// Inlined Max with 2 float arguments.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static public float Max(out int index, float a, float b) {
      if(a >= b) { index = 0; return a; }
      index = 1; return b;
    }

    //-----------------------------------------------------------------------------------

    /// <summary>
    /// Inlined Min with 3 int arguments.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static public int Min(out int index, int a, int b, int c) {
      if(a <= b && a <= c) { index = 0; return a; }
      if(b <= a && b <= c) { index = 1; return b; }
      index = 2; return c;
    }

    /// <summary>
    /// Inlined Max with 3 int arguments.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static public int Max(out int index, int a, int b, int c) {
      if(a >= b && a >= c) { index = 0; return a; }
      if(b >= a && b >= c) { index = 1; return b; }
      index = 2; return c;
    }

    /// <summary>
    /// Inlined Min with 3 float arguments.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static public float Min(out int index, float a, float b, float c) {
      if(a <= b && a <= c) { index = 0; return a; }
      if(b <= a && b <= c) { index = 1; return b; }
      index = 2; return c;
    }

    /// <summary>
    /// Inlined Max with 3 float arguments.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static public float Max(out int index, float a, float b, float c) {
      if(a >= b && a >= c) { index = 0; return a; }
      if(b >= a && b >= c) { index = 1; return b; }
      index = 2; return c;
    }

    //-----------------------------------------------------------------------------------

    /// <summary>
    /// Unconstrained generic BestOf with a custom criterion, working with IList<T>.
    /// </summary>
    static public T BestOf<T>(int startIndex, int length, Func<T, T, bool> criterion, out int index, IList<T> values) {
      if(criterion == null || values == null) throw new ArgumentNullException();
      if(startIndex < 0 || length <= 0) throw new IndexOutOfRangeException();
      if(startIndex + length > values.Count) throw new IndexOutOfRangeException();

      // default winner
      index = startIndex;

      // consider even pairs of first/last indices
      int hlen = index + (length >> 1);

      for(int i = index, j = index + length - 1; i <= hlen; i++, j--) {
        // check the first/last pair against established values, or test middle element if length is odd
        if(i < hlen || (length & 1) != 0) {
          if(criterion(values[i], values[index])) index = i;
          if(i < hlen && criterion(values[j], values[index])) index = j; // discard this check if i == hlen
        }
      }

      return values[index];
    }

    /// <summary>
    /// Unconstrained generic BestOf with a custom criterion, params variant.
    /// </summary>
    static public T BestOf<T>(int startIndex, int length, Func<T, T, bool> criterion, out int index, params T[] values) {
      if(criterion == null || values == null) throw new ArgumentNullException();
      if(startIndex < 0 || length <= 0) throw new IndexOutOfRangeException();
      if(startIndex + length > values.Length) throw new IndexOutOfRangeException();

      // default winner
      index = startIndex;

      // consider even pairs of first/last indices
      int hlen = index + (length >> 1);

      for(int i = index, j = index + length - 1; i <= hlen; i++, j--) {
        // check the first/last pair against established values, or test middle element if length is odd
        if(i < hlen || (length & 1) != 0) {
          if(criterion(values[i], values[index])) index = i;
          if(i < hlen && criterion(values[j], values[index])) index = j; // discard this check if i == hlen
        }
      }

      return values[index];
    }

    /// <summary>
    /// Constrained generic BestOf with a custom criterion, working with IList<T>.
    /// Type must implement IComparable<T>.
    /// </summary>
    static public T BestOf<T>(Func<T, T, bool> criterion, out int index, IList<T> values) where T : IComparable<T>
      => BestOf(0, values.Count, criterion, out index, values);

    /// <summary>
    /// Constrained generic BestOf with a custom criterion, params variant.
    /// Type must implement IComparable<T>.
    /// </summary>
    static public T BestOf<T>(Func<T, T, bool> criterion, out int index, params T[] values) where T : IComparable<T>
      => BestOf(0, values.Length, criterion, out index, values);

    //-----------------------------------------------------------------------------------

    /// <summary>
    /// Constrained generic Min, working with IList<T>.
    /// Type must implement IComparable<T>.
    /// </summary>
    static public T Min<T>(out int index, IList<T> values) where T : IComparable<T>
      => BestOf(0, values.Count, (T a, T b) => a.CompareTo(b) < 0, out index, values);

    /// <summary>
    /// Constrained generic Min, params variant.
    /// Type must implement IComparable<T>.
    /// </summary>
    static public T Min<T>(out int index, params T[] values) where T : IComparable<T>
      => BestOf(0, values.Length, (T a, T b) => a.CompareTo(b) < 0, out index, values);

    /// <summary>
    /// Constrained generic Max, working with IList<T>.
    /// Type must implement IComparable<T>.
    /// </summary>
    static public T Max<T>(out int index, IList<T> values) where T : IComparable<T>
      => BestOf(0, values.Count, (T a, T b) => a.CompareTo(b) > 0, out index, values);

    /// <summary>
    /// Constrained generic Max, params variant.
    /// Type must implement IComparable<T>.
    /// </summary>
    static public T Max<T>(out int index, params T[] values) where T : IComparable<T>
      => BestOf(0, values.Length, (T a, T b) => a.CompareTo(b) > 0, out index, values);

    //-----------------------------------------------------------------------------------

    /// <summary>
    /// Fast statically-typed Min, working with IList<float>.
    /// </summary>
    static public float Min(out int index, IList<float> values) {
      if(values == null) throw new ArgumentNullException();
      if(values.Count == 0) throw new IndexOutOfRangeException();

      index = 0;

      int hlen = values.Count >> 1;
      for(int i = 0, j = values.Count - 1; i < hlen; i++, j--) {
        if(values[i] < values[index]) index = i;
        if(values[j] < values[index]) index = j;
      }

      if((values.Count & 1) != 0 && values[hlen] < values[index]) index = hlen;

      return values[index];
    }

    /// <summary>
    /// Fast statically-typed Min, params variant for float arguments.
    /// </summary>
    static public float Min(out int index, params float[] values) {
      if(values == null) throw new ArgumentNullException();
      if(values.Length == 0) throw new IndexOutOfRangeException();

      index = 0;

      int hlen = values.Length >> 1;
      for(int i = 0, j = values.Length - 1; i < hlen; i++, j--) {
        if(values[i] < values[index]) index = i;
        if(values[j] < values[index]) index = j;
      }

      if((values.Length & 1) != 0 && values[hlen] < values[index]) index = hlen;

      return values[index];
    }

    /// <summary>
    /// Fast statically-typed Min, working with IList<int>.
    /// </summary>
    static public int Min(out int index, IList<int> values) {
      if(values == null) throw new ArgumentNullException();
      if(values.Count == 0) throw new IndexOutOfRangeException();

      index = 0;

      int hlen = values.Count >> 1;
      for(int i = 0, j = values.Count - 1; i < hlen; i++, j--) {
        if(values[i] < values[index]) index = i;
        if(values[j] < values[index]) index = j;
      }

      if((values.Count & 1) != 0 && values[hlen] < values[index]) index = hlen;

      return values[index];
    }

    /// <summary>
    /// Fast statically-typed Min, params variant for int arguments.
    /// </summary>
    static public int Min(out int index, params int[] values) {
      if(values == null) throw new ArgumentNullException();
      if(values.Length == 0) throw new IndexOutOfRangeException();

      index = 0;

      int hlen = values.Length >> 1;
      for(int i = 0, j = values.Length - 1; i < hlen; i++, j--) {
        if(values[i] < values[index]) index = i;
        if(values[j] < values[index]) index = j;
      }

      if((values.Length & 1) != 0 && values[hlen] < values[index]) index = hlen;

      return values[index];
    }

    //-----------------------------------------------------------------------------------

    /// <summary>
    /// Fast statically-typed Max, working with IList<float>.
    /// </summary>
    static public float Max(out int index, IList<float> values) {
      if(values == null) throw new ArgumentNullException();
      if(values.Count == 0) throw new IndexOutOfRangeException();

      index = 0;

      int hlen = values.Count >> 1;
      for(int i = 0, j = values.Count - 1; i < hlen; i++, j--) {
        if(values[i] > values[index]) index = i;
        if(values[j] > values[index]) index = j;
      }

      if((values.Count & 1) != 0 && values[hlen] > values[index]) index = hlen;

      return values[index];
    }

    /// <summary>
    /// Fast statically-typed Min, params variant for float arguments.
    /// </summary>
    static public float Max(out int index, params float[] values) {
      if(values == null) throw new ArgumentNullException();
      if(values.Length == 0) throw new IndexOutOfRangeException();

      index = 0;

      int hlen = values.Length >> 1;
      for(int i = 0, j = values.Length - 1; i < hlen; i++, j--) {
        if(values[i] > values[index]) index = i;
        if(values[j] > values[index]) index = j;
      }

      if((values.Length & 1) != 0 && values[hlen] > values[index]) index = hlen;

      return values[index];
    }

    /// <summary>
    /// Fast statically-typed Max, working with IList<int>.
    /// </summary>
    static public int Max(out int index, IList<int> values) {
      if(values == null) throw new ArgumentNullException();
      if(values.Count == 0) throw new IndexOutOfRangeException();

      index = 0;

      int hlen = values.Count >> 1;
      for(int i = 0, j = values.Count - 1; i < hlen; i++, j--) {
        if(values[i] > values[index]) index = i;
        if(values[j] > values[index]) index = j;
      }

      if((values.Count & 1) != 0 && values[hlen] > values[index]) index = hlen;

      return values[index];
    }

    /// <summary>
    /// Fast statically-typed Min, params variant for int arguments.
    /// </summary>
    static public int Max(out int index, params int[] values) {
      if(values == null) throw new ArgumentNullException();
      if(values.Length == 0) throw new IndexOutOfRangeException();

      index = 0;

      int hlen = values.Length >> 1;
      for(int i = 0, j = values.Length - 1; i < hlen; i++, j--) {
        if(values[i] > values[index]) index = i;
        if(values[j] > values[index]) index = j;
      }

      if((values.Length & 1) != 0 && values[hlen] > values[index]) index = hlen;

      return values[index];
    }

    //-----------------------------------------------------------------------------------

    /// <summary>
    /// Returns both minimum and maximum in one pass, IList<T> variant.
    /// </summary>
    static public (float min, float max) MinMax(out int minIndex, out int maxIndex, IList<float> values) {
      if(values == null) throw new ArgumentNullException();
      if(values.Count == 0) throw new IndexOutOfRangeException();

      minIndex = maxIndex = 0;

      int hlen = values.Count >> 1;
      for(int i = 0, j = values.Count - 1; i < hlen; i++, j--) {
        if(values[i] < values[minIndex]) minIndex = i;
        if(values[i] > values[maxIndex]) maxIndex = i;
        if(values[j] < values[minIndex]) minIndex = j;
        if(values[j] > values[maxIndex]) maxIndex = j;
      }

      if((values.Count & 1) != 0) {
        if(values[hlen] < values[minIndex]) minIndex = hlen;
        if(values[hlen] > values[maxIndex]) maxIndex = hlen;
      }

      return ( values[minIndex], values[maxIndex] );
    }

    /// <summary>
    /// Returns both minimum and maximum in one pass, params variant for float arguments.
    /// </summary>
    static public (float min, float max) MinMax(out int minIndex, out int maxIndex, params float[] values) {
      if(values == null) throw new ArgumentNullException();
      if(values.Length == 0) throw new IndexOutOfRangeException();

      minIndex = maxIndex = 0;

      int hlen = values.Length >> 1;
      for(int i = 0, j = values.Length - 1; i < hlen; i++, j--) {
        if(values[i] < values[minIndex]) minIndex = i;
        if(values[i] > values[maxIndex]) maxIndex = i;
        if(values[j] < values[minIndex]) minIndex = j;
        if(values[j] > values[maxIndex]) maxIndex = j;
      }

      if((values.Length & 1) != 0) {
        if(values[hlen] < values[minIndex]) minIndex = hlen;
        if(values[hlen] > values[maxIndex]) maxIndex = hlen;
      }

      return ( values[minIndex], values[maxIndex] );
    }

    /// <summary>
    /// Returns both minimum and maximum in one pass, IList<T> variant.
    /// </summary>
    static public (int min, int max) MinMax(out int minIndex, out int maxIndex, IList<int> values) {
      if(values == null) throw new ArgumentNullException();
      if(values.Count == 0) throw new IndexOutOfRangeException();

      minIndex = maxIndex = 0;

      int hlen = values.Count >> 1;
      for(int i = 0, j = values.Count - 1; i < hlen; i++, j--) {
        if(values[i] < values[minIndex]) minIndex = i;
        if(values[i] > values[maxIndex]) maxIndex = i;
        if(values[j] < values[minIndex]) minIndex = j;
        if(values[j] > values[maxIndex]) maxIndex = j;
      }

      if((values.Count & 1) != 0) {
        if(values[hlen] < values[minIndex]) minIndex = hlen;
        if(values[hlen] > values[maxIndex]) maxIndex = hlen;
      }

      return ( values[minIndex], values[maxIndex] );
    }

    /// <summary>
    /// Returns both minimum and maximum in one pass, params variant for int arguments.
    /// </summary>
    static public (int min, int max) MinMax(out int minIndex, out int maxIndex, params int[] values) {
      if(values == null) throw new ArgumentNullException();
      if(values.Length == 0) throw new IndexOutOfRangeException();

      minIndex = maxIndex = 0;

      int hlen = values.Length >> 1;
      for(int i = 0, j = values.Length - 1; i < hlen; i++, j--) {
        if(values[i] < values[minIndex]) minIndex = i;
        if(values[i] > values[maxIndex]) maxIndex = i;
        if(values[j] < values[minIndex]) minIndex = j;
        if(values[j] > values[maxIndex]) maxIndex = j;
      }

      if((values.Length & 1) != 0) {
        if(values[hlen] < values[minIndex]) minIndex = hlen;
        if(values[hlen] > values[maxIndex]) maxIndex = hlen;
      }

      return ( values[minIndex], values[maxIndex] );
    }
  }

}

Warnings:
This is not intended as a replacement for Mathf.Min and Mathf.Max.
It hasn’t been thoroughly tested and time-proven and might contain bugs/typos.
Use at your own risk, attribution not required… MIT licence.

If there are any comments, bug reports, questions, write here.

edit:
Fixed a bug in the simplest of the inlined Mins and Maxs that I’ve noticed in the meantime.
These used a wrong operator < (or >) where it really should’ve been <= (or >=).
The only place where this was a critical bug were Min/Max functions with exactly 3 arguments, especially the int variants, as having a pair of equal arguments would yield a wrong result in some cases.

Edit: see post #5 for an improved version of this utility.

The latest addition to the zoo, strictly committed to finding the nearest/farthest point among the points in the array (computes Euclidean distance). However this doesn’t really belong to that Comparator class above, this is more of a Vector3 extension.

If anyone’s interested it can be easily adapted to work with Vector2 as well, or with Manhattan distances, maybe I’ll do that later. It’s very performant, very similar in operation to the methods above, and tries to allocate as little as possible, however ONLY IF you use it by passing an array (and not by supplying arguments).

It will even spare the stack from any intermediate structs, and will also pass structs by reference internally; there is no faster way to compare distances in a series like this, as far as I know. Heap not involved (in terms of allocation).

All methods support params

myPoint.NearestOf(new Vector3(1f, 0f, 5f), new Vector3(0f, 0f, 6f), new Vector3(2f, 0f, 2f));

Intended usage

var pointsArray = new Vector3[ ... ] { ... }; // array of points
Vector3 myPoint = new Vector3(.5f, .5f, .5f); // point to compare with

// returned values
Vector3 bestPoint;
float bestDistance;
int bestIndex;

bestPoint = myPoint.NearestOf(pointsArray);
bestPoint = myPoint.NearestOf(out bestDistance, pointsArray);
bestPoint = myPoint.NearestOf(out bestIndex, pointsArray);
bestPoint = myPoint.NearestOf(out bestIndex, out bestDistance, pointsArray);

bestPoint = myPoint.FarthestOf(pointsArray);
bestPoint = myPoint.FarthestOf(out bestDistance, pointsArray);
bestPoint = myPoint.FarthestOf(out bestIndex, pointsArray);
bestPoint = myPoint.FarthestOf(out bestIndex, out bestDistance, pointsArray);

// obviously the result can be discarded, if not needed
myPoint.NearestOf(out bestIndex, out bestDistance, pointsArray);

Full code (edit: this is obsolete now, see post #5):

using UnityEngine;

static public class VectorExtensions {
 
  static private int nearest_impl(in Vector3 point, out float sqrDistance, Vector3[] points) {
    if(points == null) throw new ArgumentNullException();
    if(points.Length == 0) throw new IndexOutOfRangeException();

    int index = 0;
    sqrDistance = sqrdist(point, points[0]);

    float sqrdist(in Vector3 a, in Vector3 b) {
      float x = a.x - b.x, y = a.y - b.y, z = a.z - b.z;
      return x * x + y * y + z * z;
    }

    if(points.Length > 1) {
      int hlen = points.Length >> 1;
      float sd = 0f;
      for(int i = 0, j = points.Length - 1; i <= hlen; i++, j--) {
        if(i < hlen || (points.Length & 1) != 0) {
          if(i > 0) {
            sd = sqrdist(point, points[i]);
            if(sd < sqrDistance) { sqrDistance = sd; index = i; }
          }
          if(i < hlen) {
            sd = sqrdist(point, points[j]);
            if(sd < sqrDistance) { sqrDistance = sd; index = j; }
          }
        }
      }
    }

    return index;
  }

  static public Vector3 NearestOf(this Vector3 point, params Vector3[] points)
    => points[nearest_impl(point, out _, points)];

  static public Vector3 NearestOf(this Vector3 point, out float distance, params Vector3[] points) {
    int index = nearest_impl(point, out distance, points);
    distance = Mathf.Sqrt(distance);
    return points[index];
  }

  static public Vector3 NearestOf(this Vector3 point, out int index, params Vector3[] points) {
    index = nearest_impl(point, out _, points);
    return points[index];
  }

  static public Vector3 NearestOf(this Vector3 point, out int index, out float distance, params Vector3[] points) {
    index = nearest_impl(point, out distance, points);
    distance = Mathf.Sqrt(distance);
    return points[index];
  }

  static private int farthest_impl(in Vector3 point, out float sqrDistance, Vector3[] points) {
    if(points == null) throw new ArgumentNullException();
    if(points.Length == 0) throw new IndexOutOfRangeException();

    int index = 0;
    sqrDistance = sqrdist(point, points[0]);

    float sqrdist(in Vector3 a, in Vector3 b) {
      float x = a.x - b.x, y = a.y - b.y, z = a.z - b.z;
      return x * x + y * y + z * z;
    }

    if(points.Length > 1) {
      int hlen = points.Length >> 1;
      float sd = 0f;
      for(int i = 0, j = points.Length - 1; i <= hlen; i++, j--) {
        if(i < hlen || (points.Length & 1) != 0) {
          if(i > 0) {
            sd = sqrdist(points[i], points[index]);
            if(sd > sqrDistance) { sqrDistance = sd; index = i; }
          }
          if(i < hlen) {
            sd = sqrdist(points[j], points[index]);
            if(sd > sqrDistance) { sqrDistance = sd; index = j; }
          }
        }
      }
    }

    return index;
  }

  static public Vector3 FarthestOf(this Vector3 point, params Vector3[] points)
    => points[farthest_impl(point, out _, points)];

  static public Vector3 FarthestOf(this Vector3 point, out float distance, params Vector3[] points) {
    int index = farthest_impl(point, out distance, points);
    distance = Mathf.Sqrt(distance);
    return points[index];
  }

  static public Vector3 FarthestOf(this Vector3 point, out int index, params Vector3[] points) {
    index = farthest_impl(point, out _, points);
    return points[index];
  }

  static public Vector3 FarthestOf(this Vector3 point, out int index, out float distance, params Vector3[] points) {
    index = farthest_impl(point, out distance, points);
    distance = Mathf.Sqrt(distance);
    return points[index];
  }


}
1 Like

I’ve squashed a simple bug I’ve noticed in some of the functions listed in post #2. It affected only the simplest of the Min/Max functions with fixed arguments.

Oh, and in the meantime I’ve learned that the ‘in’ keyword is completely irrelevant because Vector3 structs are mutable and .Net would needlessly produce a defensive copy for these arguments, completely invalidating the point of the keyword in the first place (at least from one point of view, on the other, this will guarantee the struct won’t change, which is admittedly the primary point of the ‘in’ keyword). This won’t terribly affect the performance in this code above, however you are advised to remove these keywords.

For reference, the only scenario where ‘in’ would potentially significantly improve performance is on a critical path that uses relatively big readonly structs (aka immutable, that means you made one for yourself, as there’s not a single Unity-made struct that’s immutable).

When I say big, I mean probably larger than 3 ints. Say at the very least 3 Vector3s or Colors and above, but it really begins to pay off in much larger structs I doubt anyone actually uses (maybe Matrix4x4 if it weren’t made mutable; this one consumes a whopping 64 bytes, and if you pass it around too often it makes sense to recreate it as immutable with implicit conversion back to Unity’s). Obviously don’t be superstitious and benchmark the actual difference it made.

Here, I’ve decided to update my point comparators. It’s part of a larger class but should be self-contained. If it isn’t, yell at me and I’ll fix it.

Aside from the classic NearestOf/FarthestOf tests which optionally return an index and/or a distance, there are now three types of distance functions to choose from (Manhattan/Euclidean/Chebyshev), and both Vector2 and Vector3 are supported. Each combination (2D/3D + NearestOf/FarthestOf) also provides a custom evaluator, which may be something else other than distance, and all public methods have a xmldoc commentary for easier navigation.

The entire algorithm running all overloads/variants is now contained in a single method to minimize potential errors by repetition.

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;

namespace MinMaxing {
  static public class Comparator {

    // .... this is supposed to replace the code from post #3 above ...

    // NEAREST/FARTHEST COMPARATORS for VECTORS

    public enum DistanceMethod {
      Chebyshev = 0, // power: +inf
      Manhattan = 1, // power: 1
      Euclidean = 2  // power: 2
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static float max(float a, float b) => MathF.Max(a, b);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static float sgn(float v) => Math.Sign(v);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static float abs(float v) => Math.Abs(v);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static float sqrt(float v) => MathF.Sqrt(v);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static float applyInversePower(float value, int power = 0)
      => (power == 2)? sqrt(value) : value;

    /// <summary>
    /// Computes the Manhattan (taxicab) distance between two 3D points.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static public float ManhattanDistance(Vector3 a, Vector3 b)
      => abs(b.x - a.x) + abs(b.y - a.y) + abs(b.z - a.z);

    /// <summary>
    /// Computes the Manhattan (taxicab) distance between two 2D points.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static public float ManhattanDistance(Vector2 a, Vector2 b)
      => abs(b.x - a.x) + abs(b.y - a.y);

    /// <summary>
    /// Computes the squared Euclidean (geometric) distance between two 3D points.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static public float EuclideanDistance(Vector3 a, Vector3 b) {
      float x = b.x - a.x, y = b.y - a.y, z = b.z - a.z;
      return x * x + y * y + z * z;
    }

    /// <summary>
    /// Computes the squared Euclidean (geometric) distance between two 2D points.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static public float EuclideanDistance(Vector2 a, Vector2 b) {
      float x = b.x - a.x, y = b.y - a.y;
      return x * x + y * y;
    }

    /// <summary>
    /// Computes the Chebyshev (chessboard) distance between two 3D points.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static public float ChebyshevDistance(Vector3 a, Vector3 b)
      => max(max(abs(b.x - a.x), abs(b.y - a.y)), abs(b.z - a.z));

    /// <summary>
    /// Computes the Chebyshev (chessboard) distance between two 2D points.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static public float ChebyshevDistance(Vector2 a, Vector2 b)
      => max(abs(b.x - a.x), abs(b.y - a.y));

    /// <summary>
    /// Returns a reference to the selected 3D distance function.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static public Func<Vector3, Vector3, float> Distance3D(DistanceMethod method) {
      switch((int)method) {
          case 1: return (a,b) => ManhattanDistance(a, b);
          case 2: return (a,b) => EuclideanDistance(a, b);
        default: return (a,b) => ChebyshevDistance(a, b);
      }
    }

    /// <summary>
    /// Returns a reference to the selected 2D distance function.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static public Func<Vector2, Vector2, float> Distance2D(DistanceMethod method) {
      switch((int)method) {
          case 1: return (a,b) => ManhattanDistance(a, b);
          case 2: return (a,b) => EuclideanDistance(a, b);
        default: return (a,b) => ChebyshevDistance(a, b);
      }
    }

    //----- Core method -----

    private const float FARTHEST = 1f;
    private const float NEAREST = -1f;

    static private int nearfar_core_impl<V>(float sign, V point, out float best, IList<V> points, Func<V, V, float> df) /* where V : Vector2 or Vector3 */ {
      if(points is null) throw new ArgumentNullException(); // point list mandatory
      if(df is null) throw new ArgumentNullException();     // distance function mandatory

      best = 0f;
      int index = -1;

      int len = points.Count;
      if(len == 0) return index;

      var odd = (len & 1) != 0;
      best = df(point, points[0]);
      index = 0;

      if(len == 1) return index;

      int hlen = len >> 1;
      var dist = 0f;

      for(int i = 0, j = len - 1; i <= hlen; i++, j--) {
        if(i < hlen || odd) {
          if(i > 0) {
            dist = df(point, points[i]);
            if(sgn(dist - best) * sign >= 0f) { best = dist; index = i; }
          }
          if(i < hlen) {
            dist = df(point, points[j]);
            if(sgn(dist - best) * sign >= 0f) { best = dist; index = j; }
          }
        }
      }

      return index;
    }

    //-------- Vector3 --------

    /// <summary>
    /// Returns a point that is nearest to the specified point in 3D.
    /// The result is guaranteed to have the lowest Euclidean distance.
    /// </summary>
    static public Vector3 NearestOf(this Vector3 point, params Vector3[] points)
      => point.NearestOf(points, DistanceMethod.Euclidean);

    /// <summary>
    /// Returns a point that is nearest to the specified point in 3D.
    /// The result is guaranteed to have the lowest Euclidean distance.
    /// </summary>
    /// <param name="distance">Distance of the winning point.</param>
    static public Vector3 NearestOf(this Vector3 point, out float distance, params Vector3[] points)
      => point.NearestOf(out distance, points, DistanceMethod.Euclidean);

    /// <summary>
    /// Returns a point that is nearest to the specified point in 3D.
    /// The result is guaranteed to have the lowest Euclidean distance.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    static public Vector3 NearestOf(this Vector3 point, out int index, params Vector3[] points)
      => point.NearestOf(out index, points, DistanceMethod.Euclidean);

    /// <summary>
    /// Returns a point that is nearest to the specified point in 3D.
    /// The result is guaranteed to have the lowest Euclidean distance.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="distance">Distance of the winning point.</param>
    static public Vector3 NearestOf(this Vector3 point, out int index, out float distance, params Vector3[] points)
      => point.NearestOf(out index, out distance, points, DistanceMethod.Euclidean);

    /// <summary>
    /// Returns a point that is nearest to the specified point in 3D.
    /// The result is guaranteed to have the lowest distance value.
    /// </summary>
    /// <param name="method">Method to use for distance computation.</param>
    static public Vector3 NearestOf(this Vector3 point, DistanceMethod method, params Vector3[] points)
      => point.NearestOf(points, method);

    /// <summary>
    /// Returns a point that is nearest to the specified point in 3D.
    /// The result is guaranteed to have the lowest distance value.
    /// </summary>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="method">Method to use for distance computation.</param>
    static public Vector3 NearestOf(this Vector3 point, out float distance, DistanceMethod method, params Vector3[] points)
      => point.NearestOf(out distance, points, method);

    /// <summary>
    /// Returns a point that is nearest to the specified point in 3D.
    /// The result is guaranteed to have the lowest distance value.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="method">Method to use for distance computation.</param>
    static public Vector3 NearestOf(this Vector3 point, out int index, DistanceMethod method, params Vector3[] points)
      => point.NearestOf(out index, points, method);

    /// <summary>
    /// Returns a point that is nearest to the specified point in 3D.
    /// The result is guaranteed to have the lowest distance value.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="method">Method to use for distance computation.</param>
    static public Vector3 NearestOf(this Vector3 point, out int index, out float distance, DistanceMethod method, params Vector3[] points)
      => point.NearestOf(out index, out distance, points, method);

    /// <summary>
    /// Returns a 3D point after comparing each point in a IList collection to the specified one.
    /// The result is guaranteed to have the lowest value produced by the custom evaluator.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="customDistanceEval">Custom evaluation to use in place of distance computation. Use finalizer to avoid having to use MathF.Sqrt for example.</param>
    /// <param name="finalDistanceEval">Optional. Value finalizer. For example MathF.Sqrt for Euclidean distance.</param>
    static public Vector3 NearestOf(this Vector3 point, out int index, out float distance, IList<Vector3> points, Func<Vector3, Vector3, float> customDistanceEval, Func<float, float> finalDistanceEval = null) {
      index = nearfar_core_impl(NEAREST, point, out var value, points, customDistanceEval);
      distance = finalDistanceEval is null? value : finalDistanceEval(value);
      return points[index];
    }

    /// <summary>
    /// Returns a point that is nearest to the specified point in 3D.
    /// The result is guaranteed to have the lowest distance value.
    /// </summary>
    /// <param name="method">Method to use for distance computation. Default: Euclidean.</param>
    static public Vector3 NearestOf(this Vector3 point, IList<Vector3> points, DistanceMethod method = DistanceMethod.Euclidean)
      => points[nearfar_core_impl(NEAREST, point, out _, points, Distance3D(method))];

    /// <summary>
    /// Returns a point that is nearest to the specified point in 3D.
    /// The result is guaranteed to have the lowest distance value.
    /// </summary>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="method">Method to use for distance computation. Default: Euclidean.</param>
    static public Vector3 NearestOf(this Vector3 point, out float distance, IList<Vector3> points, DistanceMethod method = DistanceMethod.Euclidean) {
      int index = nearfar_core_impl(NEAREST, point, out var value, points, Distance3D(method));
      distance = applyInversePower(value, (int)method);
      return points[index];
    }

    /// <summary>
    /// Returns a point that is nearest to the specified point in 3D.
    /// The result is guaranteed to have the lowest distance value.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="method">Method to use for distance computation. Default: Euclidean.</param>
    static public Vector3 NearestOf(this Vector3 point, out int index, IList<Vector3> points, DistanceMethod method = DistanceMethod.Euclidean) {
      index = nearfar_core_impl(NEAREST, point, out _, points, Distance3D(method));
      return points[index];
    }

    /// <summary>
    /// Returns a point that is nearest to the specified point in 3D.
    /// The result is guaranteed to have the lowest distance value.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="method">Method to use for distance computation. Default: Euclidean.</param>
    static public Vector3 NearestOf(this Vector3 point, out int index, out float distance, IList<Vector3> points, DistanceMethod method = DistanceMethod.Euclidean) {
      index = nearfar_core_impl(NEAREST, point, out var value, points, Distance3D(method));
      distance = applyInversePower(value, (int)method);
      return points[index];
    }

    /// <summary>
    /// Returns a point that is farthest from the specified point in 3D.
    /// The result is guaranteed to have the highest Euclidean distance.
    /// </summary>
    static public Vector3 FarthestOf(this Vector3 point, params Vector3[] points)
      => point.FarthestOf(points, DistanceMethod.Euclidean);

    /// <summary>
    /// Returns a point that is farthest from the specified point in 3D.
    /// The result is guaranteed to have the highest Euclidean distance.
    /// </summary>
    /// <param name="distance">Distance of the winning point.</param>
    static public Vector3 FarthestOf(this Vector3 point, out float distance, params Vector3[] points)
      => point.FarthestOf(out distance, points, DistanceMethod.Euclidean);

    /// <summary>
    /// Returns a point that is farthest from the specified point in 3D.
    /// The result is guaranteed to have the highest Euclidean distance.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    static public Vector3 FarthestOf(this Vector3 point, out int index, params Vector3[] points)
      => point.FarthestOf(out index, points, DistanceMethod.Euclidean);

    /// <summary>
    /// Returns a point that is farthest from the specified point in 3D.
    /// The result is guaranteed to have the highest Euclidean distance.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="distance">Distance of the winning point.</param>
    static public Vector3 FarthestOf(this Vector3 point, out int index, out float distance, params Vector3[] points)
      => point.FarthestOf(out index, out distance, points, DistanceMethod.Euclidean);

    /// <summary>
    /// Returns a point that is farthest from the specified point in 3D.
    /// The result is guaranteed to have the highest distance value.
    /// </summary>
    /// <param name="method">Method to use for distance computation.</param>
    static public Vector3 FarthestOf(this Vector3 point, DistanceMethod method, params Vector3[] points)
      => point.FarthestOf(points, method);

    /// <summary>
    /// Returns a point that is farthest from the specified point in 3D.
    /// The result is guaranteed to have the highest distance value.
    /// </summary>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="method">Method to use for distance computation.</param>
    static public Vector3 FarthestOf(this Vector3 point, out float distance, DistanceMethod method, params Vector3[] points)
      => point.FarthestOf(out distance, points, method);

    /// <summary>
    /// Returns a point that is farthest from the specified point in 3D.
    /// The result is guaranteed to have the highest distance value.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="method">Method to use for distance computation.</param>
    static public Vector3 FarthestOf(this Vector3 point, out int index, DistanceMethod method, params Vector3[] points)
      => point.FarthestOf(out index, points, method);

    /// <summary>
    /// Returns a point that is farthest from the specified point in 3D.
    /// The result is guaranteed to have the highest distance value.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="method">Method to use for distance computation.</param>
    static public Vector3 FarthestOf(this Vector3 point, out int index, out float distance, DistanceMethod method, params Vector3[] points)
      => point.FarthestOf(out index, out distance, points, method);

    /// <summary>
    /// Returns a 3D point after comparing each point in a IList collection to the specified one.
    /// The result is guaranteed to have the highest value produced by the custom evaluator.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="customDistanceEval">Custom evaluation to use in place of distance computation. Use finalizer to avoid having to use MathF.Sqrt for example.</param>
    /// <param name="finalDistanceEval">Optional. Value finalizer. For example MathF.Sqrt for Euclidean distance.</param>
    static public Vector3 FarthestOf(this Vector3 point, out int index, out float distance, IList<Vector3> points, Func<Vector3, Vector3, float> customDistanceEval, Func<float, float> finalDistanceEval = null) {
      index = nearfar_core_impl(FARTHEST, point, out var value, points, customDistanceEval);
      distance = finalDistanceEval is null? value : finalDistanceEval(value);
      return points[index];
    }

    /// <summary>
    /// Returns a point that is farthest from the specified point in 3D.
    /// The result is guaranteed to have the highest distance value.
    /// </summary>
    /// <param name="method">Method to use for distance computation. Default: Euclidean.</param>
    static public Vector3 FarthestOf(this Vector3 point, IList<Vector3> points, DistanceMethod method = DistanceMethod.Euclidean)
      => points[nearfar_core_impl(FARTHEST, point, out _, points, Distance3D(method))];

    /// <summary>
    /// Returns a point that is farthest from the specified point in 3D.
    /// The result is guaranteed to have the highest distance value.
    /// </summary>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="method">Method to use for distance computation. Default: Euclidean.</param>
    static public Vector3 FarthestOf(this Vector3 point, out float distance, IList<Vector3> points, DistanceMethod method = DistanceMethod.Euclidean) {
      int index = nearfar_core_impl(FARTHEST, point, out var value, points, Distance3D(method));
      distance = applyInversePower(value, (int)method);
      return points[index];
    }

    /// <summary>
    /// Returns a point that is farthest from the specified point in 3D.
    /// The result is guaranteed to have the highest distance value.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="method">Method to use for distance computation. Default: Euclidean.</param>
    static public Vector3 FarthestOf(this Vector3 point, out int index, IList<Vector3> points, DistanceMethod method = DistanceMethod.Euclidean) {
      index = nearfar_core_impl(FARTHEST, point, out _, points, Distance3D(method));
      return points[index];
    }

    /// <summary>
    /// Returns a point that is farthest from the specified point in 3D.
    /// The result is guaranteed to have the highest distance value.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="method">Method to use for distance computation. Default: Euclidean.</param>
    static public Vector3 FarthestOf(this Vector3 point, out int index, out float distance, IList<Vector3> points, DistanceMethod method = DistanceMethod.Euclidean) {
      index = nearfar_core_impl(FARTHEST, point, out var value, points, Distance3D(method));
      distance = applyInversePower(value, (int)method);
      return points[index];
    }

    //-------- Vector2 --------

    /// <summary>
    /// Returns a point that is nearest to the specified point in 2D.
    /// The result is guaranteed to have the lowest Euclidean distance.
    /// </summary>
    static public Vector2 NearestOf(this Vector2 point, params Vector2[] points)
      => point.NearestOf(points, DistanceMethod.Euclidean);

    /// <summary>
    /// Returns a point that is nearest to the specified point in 2D.
    /// The result is guaranteed to have the lowest Euclidean distance.
    /// </summary>
    /// <param name="distance">Distance of the winning point.</param>
    static public Vector2 NearestOf(this Vector2 point, out float distance, params Vector2[] points)
      => point.NearestOf(out distance, points, DistanceMethod.Euclidean);

    /// <summary>
    /// Returns a point that is nearest to the specified point in 2D.
    /// The result is guaranteed to have the lowest Euclidean distance.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    static public Vector2 NearestOf(this Vector2 point, out int index, params Vector2[] points)
      => point.NearestOf(out index, points, DistanceMethod.Euclidean);

    /// <summary>
    /// Returns a point that is nearest to the specified point in 2D.
    /// The result is guaranteed to have the lowest Euclidean distance.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="distance">Distance of the winning point.</param>
    static public Vector2 NearestOf(this Vector2 point, out int index, out float distance, params Vector2[] points)
      => point.NearestOf(out index, out distance, points, DistanceMethod.Euclidean);

    /// <summary>
    /// Returns a point that is nearest to the specified point in 2D.
    /// The result is guaranteed to have the lowest distance value.
    /// </summary>
    /// <param name="method">Method to use for distance computation.</param>
    static public Vector2 NearestOf(this Vector2 point, DistanceMethod method, params Vector2[] points)
      => point.NearestOf(points, method);

    /// <summary>
    /// Returns a point that is nearest to the specified point in 2D.
    /// The result is guaranteed to have the lowest distance value.
    /// </summary>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="method">Method to use for distance computation.</param>
    static public Vector2 NearestOf(this Vector2 point, out float distance, DistanceMethod method, params Vector2[] points)
      => point.NearestOf(out distance, points, method);

    /// <summary>
    /// Returns a point that is nearest to the specified point in 2D.
    /// The result is guaranteed to have the lowest distance value.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="method">Method to use for distance computation.</param>
    static public Vector2 NearestOf(this Vector2 point, out int index, DistanceMethod method, params Vector2[] points)
      => point.NearestOf(out index, points, method);

    /// <summary>
    /// Returns a point that is nearest to the specified point in 2D.
    /// The result is guaranteed to have the lowest distance value.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="method">Method to use for distance computation.</param>
    static public Vector2 NearestOf(this Vector2 point, out int index, out float distance, DistanceMethod method, params Vector2[] points)
      => point.NearestOf(out index, out distance, points, method);

    /// <summary>
    /// Returns a 2D point after comparing each point in a IList collection to the specified one.
    /// The result is guaranteed to have the lowest value produced by the custom evaluator.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="customDistanceEval">Custom evaluation to use in place of distance computation. Use finalizer to avoid having to use MathF.Sqrt for example.</param>
    /// <param name="finalDistanceEval">Optional. Value finalizer. For example MathF.Sqrt for Euclidean distance.</param>
    static public Vector2 NearestOf(this Vector2 point, out int index, out float distance, IList<Vector2> points, Func<Vector2, Vector2, float> customDistanceEval, Func<float, float> finalDistanceEval = null) {
      index = nearfar_core_impl(NEAREST, point, out var value, points, customDistanceEval);
      distance = finalDistanceEval is null? value : finalDistanceEval(value);
      return points[index];
    }

    /// <summary>
    /// Returns a point that is nearest to the specified point in 2D.
    /// The result is guaranteed to have the lowest distance value.
    /// </summary>
    /// <param name="method">Method to use for distance computation. Default: Euclidean.</param>
    static public Vector2 NearestOf(this Vector2 point, IList<Vector2> points, DistanceMethod method = DistanceMethod.Euclidean)
      => points[nearfar_core_impl(NEAREST, point, out _, points, Distance2D(method))];

    /// <summary>
    /// Returns a point that is nearest to the specified point in 2D.
    /// The result is guaranteed to have the lowest distance value.
    /// </summary>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="method">Method to use for distance computation. Default: Euclidean.</param>
    static public Vector2 NearestOf(this Vector2 point, out float distance, IList<Vector2> points, DistanceMethod method = DistanceMethod.Euclidean) {
      int index = nearfar_core_impl(NEAREST, point, out var value, points, Distance2D(method));
      distance = applyInversePower(value, (int)method);
      return points[index];
    }

    /// <summary>
    /// Returns a point that is nearest to the specified point in 2D.
    /// The result is guaranteed to have the lowest distance value.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="method">Method to use for distance computation. Default: Euclidean.</param>
    static public Vector2 NearestOf(this Vector2 point, out int index, IList<Vector2> points, DistanceMethod method = DistanceMethod.Euclidean) {
      index = nearfar_core_impl(NEAREST, point, out _, points, Distance2D(method));
      return points[index];
    }

    /// <summary>
    /// Returns a point that is nearest to the specified point in 2D.
    /// The result is guaranteed to have the lowest distance value.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="method">Method to use for distance computation. Default: Euclidean.</param>
    static public Vector2 NearestOf(this Vector2 point, out int index, out float distance, IList<Vector2> points, DistanceMethod method = DistanceMethod.Euclidean) {
      index = nearfar_core_impl(NEAREST, point, out var value, points, Distance2D(method));
      distance = applyInversePower(value, (int)method);
      return points[index];
    }

    /// <summary>
    /// Returns a point that is farthest from the specified point in 2D.
    /// The result is guaranteed to have the highest Euclidean distance.
    /// </summary>
    static public Vector2 FarthestOf(this Vector2 point, params Vector2[] points)
      => point.FarthestOf(points, DistanceMethod.Euclidean);

    /// <summary>
    /// Returns a point that is farthest from the specified point in 2D.
    /// The result is guaranteed to have the highest Euclidean distance.
    /// </summary>
    /// <param name="distance">Distance of the winning point.</param>
    static public Vector2 FarthestOf(this Vector2 point, out float distance, params Vector2[] points)
      => point.FarthestOf(out distance, points, DistanceMethod.Euclidean);

    /// <summary>
    /// Returns a point that is farthest from the specified point in 2D.
    /// The result is guaranteed to have the highest Euclidean distance.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    static public Vector2 FarthestOf(this Vector2 point, out int index, params Vector2[] points)
      => point.FarthestOf(out index, points, DistanceMethod.Euclidean);

    /// <summary>
    /// Returns a point that is farthest from the specified point in 2D.
    /// The result is guaranteed to have the highest Euclidean distance.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="distance">Distance of the winning point.</param>
    static public Vector2 FarthestOf(this Vector2 point, out int index, out float distance, params Vector2[] points)
      => point.FarthestOf(out index, out distance, points, DistanceMethod.Euclidean);

    /// <summary>
    /// Returns a point that is farthest from the specified point in 2D.
    /// The result is guaranteed to have the highest distance value.
    /// </summary>
    /// <param name="method">Method to use for distance computation.</param>
    static public Vector2 FarthestOf(this Vector2 point, DistanceMethod method, params Vector2[] points)
      => point.FarthestOf(points, method);

    /// <summary>
    /// Returns a point that is farthest from the specified point in 2D.
    /// The result is guaranteed to have the highest distance value.
    /// </summary>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="method">Method to use for distance computation.</param>
    static public Vector2 FarthestOf(this Vector2 point, out float distance, DistanceMethod method, params Vector2[] points)
      => point.FarthestOf(out distance, points, method);

    /// <summary>
    /// Returns a point that is farthest from the specified point in 2D.
    /// The result is guaranteed to have the highest distance value.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="method">Method to use for distance computation.</param>
    static public Vector2 FarthestOf(this Vector2 point, out int index, DistanceMethod method, params Vector2[] points)
      => point.FarthestOf(out index, points, method);

    /// <summary>
    /// Returns a point that is farthest from the specified point in 2D.
    /// The result is guaranteed to have the highest distance value.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="method">Method to use for distance computation.</param>
    static public Vector2 FarthestOf(this Vector2 point, out int index, out float distance, DistanceMethod method, params Vector2[] points)
      => point.FarthestOf(out index, out distance, points, method);

    /// <summary>
    /// Returns a 2D point after comparing each point in a IList collection to the specified one.
    /// The result is guaranteed to have the highest value produced by the custom evaluator.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="customDistanceEval">Custom evaluation to use in place of distance computation. Use finalizer to avoid having to use MathF.Sqrt for example.</param>
    /// <param name="finalDistanceEval">Optional. Value finalizer. For example MathF.Sqrt for Euclidean distance.</param>
    static public Vector2 FarthestOf(this Vector2 point, out int index, out float distance, IList<Vector2> points, Func<Vector2, Vector2, float> customDistanceEval, Func<float, float> finalDistanceEval = null) {
      index = nearfar_core_impl(FARTHEST, point, out var value, points, customDistanceEval);
      distance = finalDistanceEval is null? value : finalDistanceEval(value);
      return points[index];
    }

    /// <summary>
    /// Returns a point that is farthest from the specified point in 2D.
    /// The result is guaranteed to have the highest distance value.
    /// </summary>
    /// <param name="method">Method to use for distance computation. Default: Euclidean.</param>
    static public Vector2 FarthestOf(this Vector2 point, IList<Vector2> points, DistanceMethod method = DistanceMethod.Euclidean)
      => points[nearfar_core_impl(FARTHEST, point, out _, points, Distance2D(method))];

    /// <summary>
    /// Returns a point that is farthest from the specified point in 2D.
    /// The result is guaranteed to have the highest distance value.
    /// </summary>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="method">Method to use for distance computation. Default: Euclidean.</param>
    static public Vector2 FarthestOf(this Vector2 point, out float distance, IList<Vector2> points, DistanceMethod method = DistanceMethod.Euclidean) {
      int index = nearfar_core_impl(FARTHEST, point, out var value, points, Distance2D(method));
      distance = applyInversePower(value, (int)method);
      return points[index];
    }

    /// <summary>
    /// Returns a point that is farthest from the specified point in 2D.
    /// The result is guaranteed to have the highest distance value.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="method">Method to use for distance computation. Default: Euclidean.</param>
    static public Vector2 FarthestOf(this Vector2 point, out int index, IList<Vector2> points, DistanceMethod method = DistanceMethod.Euclidean) {
      index = nearfar_core_impl(FARTHEST, point, out _, points, Distance2D(method));
      return points[index];
    }

    /// <summary>
    /// Returns a point that is farthest from the specified point in 2D.
    /// The result is guaranteed to have the highest distance value.
    /// </summary>
    /// <param name="index">Index of the winning point.</param>
    /// <param name="distance">Distance of the winning point.</param>
    /// <param name="method">Method to use for distance computation. Default: Euclidean.</param>
    static public Vector2 FarthestOf(this Vector2 point, out int index, out float distance, IList<Vector2> points, DistanceMethod method = DistanceMethod.Euclidean) {
      index = nearfar_core_impl(FARTHEST, point, out var value, points, Distance2D(method));
      distance = applyInversePower(value, (int)method);
      return points[index];
    }
  }
}

Cheers

1 Like