-3

Say I have an grid of 100by100.

Now I have a location on 23,70.

How can I find all XY points on this grid within a distance of 5?

  • 2
    How about a the [Burkhard-Keller tree algorithm](https://en.wikipedia.org/wiki/BK-tree)? I wrote a C# implementation [on github](https://github.com/biggyspender/BkTree). It's a neat fit to your needs. Look at the readme to see how to use it. – spender Aug 05 '18 at 23:25
  • I would like to add some detail to spender response.You can compare every point in you grid against the location 23,70 to get you answer but would required performing 10,000 (100x100) tests.The BK-tree would reduce the number of tests, but you would have to build the tree which takes resources.If you are doing only one comparison it would not pay to build the tree.Also just building the tree does nothing unless you create the methods to reduce the number of comparisons.See link on spender referenced webpage you should look at: http://blog.notdot.net/2007/4/Damn-Cool-Algorithms-Part-1-BK-Trees – jdweng Aug 06 '18 at 00:01

1 Answers1

0

Your question is a little vague, but there might be some simple methods to do it quickly depending on your requirements and definitions.

Assuming that we're talking about a unit-square grid there are 3 primary ways to define the distance:

  1. Number of steps in the 8 directions: n, ne, e, se, s, sw, w, nw
  2. Number of steps in the 4 directions: n, e, s, w
  3. Linear distance between the cells (Pythagoras)

The first is the simplest, being simply a square from [x - 5, y - 5] to [x + 5, y + 5]. Quick enough to calculate on the fly.

Second option is a diamond shape which satisfies the following function:

static bool InRange(Point C, Point P) => (Math.Abs(P.X - C.X) + Math.Abs(P.Y - C.Y)) <= 5;

And finally, linear distance uses Pythagoras to find the distance between the cells and compares that to your threshold value. The final shape will depend on how you handle overlap. You could use the following test function:

static bool InRange(Point C, Point P) => ((P.X - C.X) * (P.X - C.X)) + ((P.Y - C.Y) * (P.Y - C.Y)) <= 25;

Note that in all cases the possible solution range falls in the square defined by the first solution, so you don't have to test every single cell on your grid. At most there are 24 tests (for a distance of 5) modified by the size of the grid.

Here's a general solution building on the above, using LINQ because I like it:

public static IEnumerable<Point> PointsInRange(this Point C, int range, int method)
{
    // select filter function
    Func<int, int, bool> filter = (x, y) => true;
    if (method == 1)
        filter = (x, y) => (Math.Abs(x) + Math.Abs(y)) <= range;
    else if (method == 2)
        filter = (x, y) => (x * x + y * y) <= (range * range);

    // apply filter to all cells in potential range
    var validOffsets = 
        from dx in Enumerable.Range(-range, range * 2 + 1)
        from dy in Enumerable.Range(-range, range * 2 + 1)
        where (dx != 0 || dy != 0) && filter(dx, dy)
        select new { dx, dy };

    // offset from center and clip to grid
    var result = 
        from o in validOffsets
        let x = C.x + o.dx
        let y = C.y + o.dy
        where x >= 0 && x < 100 && y >= 0 && y < 100
        select new Point(x, y);

    return result;
}

There are a few speed-ups you can apply, as always.

Of course if you only ever want to get the valid cells for a specific range (5 for instance) then you can reduce it further by specifying the valid offsets in an array instead of calculating them as above. You can cut the whole start of the method and replace it with a constant array with the shape you want. Or combine the two ideas and cache the answers so that you have the flexibility of arbitrary range and the speed of computing the valid offsets only once.

Corey
  • 15,524
  • 2
  • 35
  • 68