Skip to content

Instantly share code, notes, and snippets.

@AndreaLanfranchi
Last active July 25, 2024 09:28
Show Gist options
  • Select an option

  • Save AndreaLanfranchi/4423afa60cf0d41ffc60627b49c4436f to your computer and use it in GitHub Desktop.

Select an option

Save AndreaLanfranchi/4423afa60cf0d41ffc60627b49c4436f to your computer and use it in GitHub Desktop.
Loop vs Linq to find nearest side
using System.Drawing;

internal class ExtendedRect {

    private Rectangle _rect;

    private sealed record SideDistance(int Side, int Distance);

    public long Area {
        get {
            return _rect.Width * _rect.Height;
        }
    }

    #region Factory

    public ExtendedRect(Rectangle rect) {
        _rect = new(rect.Location, rect.Size);
    }

    public ExtendedRect(int x, int y, int width, int height) {
        _rect = new(x, y, width, height);
    }

    public ExtendedRect(Point location, Size size) {
        _rect = new(location, size);
    }

    #endregion Factory

    public Size Size => _rect.Size;
    public Point Location => _rect.Location;
    public bool IsEmpty => _rect.IsEmpty;


    /// <summary>
    /// Given a two dimensional space point it tries to find which side of the embedded
    /// rectangle is the nearest to the point itself.
    /// </summary>
    /// <param name="point">A <see cref="Point"/></param>
    /// <param name="distanceThreshold">The maximum distance value allowed to consider a side as "near" the provided point</param>
    /// <returns>-1 when no side found matching the criteria or a value in [0,3]</returns>
    /// <remarks>
    /// - Whether the point is contained in the rectangle is not relevant
    /// - A rectangle with zero area will always return -1 (there are no sides)
    /// - The value returned when != -1 is the index of the matching side in clockwise order starting from Left
    /// </remarks>
    public int FindNearestSideLoop(Point point, int distanceThreshold = 25) {
        int ret = -1;
        if (Area == 0 || (point.X < 0 || point.Y < 0)) { return ret; }

        for (int i = 0; i < 4; ++i) {
            int distance = i switch {
                0 => Math.Abs(_rect.Left - point.X),
                1 => Math.Abs(_rect.Top - point.Y),
                2 => Math.Abs(_rect.Right - point.X),
                3 => Math.Abs(_rect.Bottom - point.Y),
            };

            if (distance < distanceThreshold) {
                ret = i;
                distanceThreshold = distance;
                if (distanceThreshold < 1) {
                    break; // Found a point ON a side.
                }
            }
        }
        return ret;
    }

    public int FindNearestSideLinq(Point point, int distanceThreshold = 25) {

        if (Area == 0 || (point.X < 0 || point.Y < 0)) { return -1; }
        var distances = new List<SideDistance> {
            new(0, Math.Abs(_rect.Left - point.X)),
            new(1, Math.Abs(_rect.Top - point.Y)),
            new(2, Math.Abs(_rect.Right - point.X)),
            new(3, Math.Abs(_rect.Bottom - point.Y))
        };

        return distances.Where(d => d.Distance < distanceThreshold).OrderBy(d => d.Distance).FirstOrDefault()?.Side ?? -1;
    }

}

class Program {

    static void Main() {

        var TargetRect = new ExtendedRect(1024, 1024, 8192, 8192);
        var RegionRect = new Rectangle(0, 0, TargetRect.Size.Width + 2048, TargetRect.Size.Height + 2048);

        // Create a list of unique points randomly picked from RegionRect
        var random = new Random();
        var points = new HashSet<Point>();
        while (points.Count < 100_000) {
            _ = points.Add(new Point(random.Next(RegionRect.Left, RegionRect.Right), random.Next(RegionRect.Top, RegionRect.Bottom)));
        }

        Console.WriteLine($"Points To Test: {points.Count}");

        int matchings = 0;
        int misses = 0;
        var foundSides = new List<int>();

        var watch = System.Diagnostics.Stopwatch.StartNew();
        foreach (var point in points) {
            foundSides.Add(TargetRect.FindNearestSideLoop(point));
        }
        watch.Stop();
        matchings = foundSides.Count(f => f != -1);
        misses = foundSides.Count(f => f == -1);
        Console.Write($"Loop: {watch.ElapsedMilliseconds}ms matches:{matchings} misses:{misses}\n");

        foundSides.Clear();
        watch.Restart();
        foreach (var point in points) {
            foundSides.Add(TargetRect.FindNearestSideLinq(point));
        }
        watch.Stop();
        matchings = foundSides.Count(f => f != -1);
        misses = foundSides.Count(f => f == -1);
        Console.Write($"Linq: {watch.ElapsedMilliseconds}ms matches:{matchings} misses:{misses}\n");

    }
}

Results :

Points To Test: 100000
Loop: 7ms matches:1924 misses:98076
Linq: 42ms matches:1924 misses:98076
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment