I have a game code,is working and doing:
*Spawn one animal in a floor tile when tile is accepted,but doing in all accepted tiles.
I want the script do it just one time,here the code:
public static void Generate( Rectangle2D region )
{
int OakTree = 0x12B9;
for ( int rx = 0; rx < region.Width; ++rx )
{
for ( int ry = 0; ry < region.Height; ++ry )
{
int vx = rx + region.X;
int vy = ry + region.Y;
StaticTile[] tiles = m_Map.Tiles.GetStaticTiles( vx, vy );
for ( int i = 0; i < tiles.Length; ++i )
{
StaticTile tile = tiles[i];
int id = tile.ID;
id &= 0x3FFF;
int z = tile.Z;
if ( IsRock( id ) )
{
AddAnimalSpawner( vx + 1, vy, z );
}
}
}
}
}
What is the correct way to somply it to just one time?I think the problem is for ( int i = 0; i < tiles.Length; ++i ) .
Thank you!
If I understood you correctly you want to call AddAnimalSpawner just one time.
You can simply use the break keyword. (https://learn.microsoft.com/de-de/dotnet/csharp/language-reference/keywords/break) to return to the outer loops or return to exit the Generate(Rectangle2D region) Function.
Example:
for ( int i = 0; i < tiles.Length; ++i )
{
StaticTile tile = tiles[i];
int id = tile.ID;
id &= 0x3FFF;
int z = tile.Z;
if ( IsRock( id ) )
{
AddAnimalSpawner( vx + 1, vy, z );
break;
}
}
or
for ( int i = 0; i < tiles.Length; ++i )
{
StaticTile tile = tiles[i];
int id = tile.ID;
id &= 0x3FFF;
int z = tile.Z;
if ( IsRock( id ) )
{
AddAnimalSpawner( vx + 1, vy, z );
return;
}
}
As you are already mentioning: you are getting a collection of tiles (by calling GetStaticTiles), and then you add an animal to all of them after verifying that the tile is rock (I guess this has to do with other kind of tiles, like water, where the animal cannot be placed, right?).
In order to do this only one time, you have to break the for look after adding the animal the first time. Here you have several options. One of them is to rewrite your code to better describe what you are doing, but the simpler option you have may be to simply set
i = tiles.Length;
after calling AddAnimalSpawner. This will end the for loop in the next iteration (i.e.: almost immediately) after placing only one animal.
Note that by following the latter method, the outer loop (the one iterating over the regions) will still run. If you don't want that to happen, then you can simply return from the function after adding the only animal.
Related
I have an implementation of BFS that works just fine, but seems to get really CPU heavy, even at low depth (sub ms for a depth of 4 but 10 ms for a depth of 10). I'm quite confident that this algorithm should run sub ms event at a depth of 100 but i'm not quite sure what I'm missing.
Here's the code :
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class VisionGraph : MonoBehaviour
{
public Transform Ground;
public int Height;
public int Width;
private MeshFilter mf;
public Vector3[] Vertices;
public float precision;
public Vector3 SelectedVertex;
// Start is called before the first frame update
private void Start()
{
mf = Ground.GetComponent<MeshFilter>();
Matrix4x4 localToWorld = transform.localToWorldMatrix;
Vector3 world_max = localToWorld.MultiplyPoint3x4(mf.mesh.vertices[0]);
Width = Mathf.RoundToInt(world_max.z * (1 / precision));
int maxIndex = Mathf.RoundToInt((world_max.z * (1 / precision)) * (Height * (1 / precision)) * world_max.x);
Vertices = new Vector3[maxIndex];
//This is the graph initialization.
//Indices increment by 1 while actual coordinates increments by `precision`.
//Indices are then reversed to coordinates in BFS with `position/precision`.
int xInd, yInd, zInd; xInd = yInd = zInd = 0;
float x, y, z; x = y = z = 0;
int index = 0;
while (index < Vertices.Length - 1)
{
index = (yInd * (Width * Width)) + (zInd * Width) + xInd;
Debug.Log(index + " " + maxIndex);
Vertices[index] = new(x, y, z);
x += precision;
xInd++;
if (x > world_max.x)
{
x = 0;
xInd = 0;
z += precision;
zInd++;
if (z > world_max.z)
{
z = 0;
zInd = 0;
y += precision;
yInd++;
}
}
}
SelectedVertex = Vertices[600];
}
private void OnDrawGizmos()
{
// Needs to be turned into retrieve index from position.
// but i'm not sure how to clamp the continuous position to `precision` steps.
SelectedVertex = Vertices.Where(v => Vector3.Distance(v, SelectedVertex) <= precision - 0.0001 ).FirstOrDefault();
var watch = System.Diagnostics.Stopwatch.StartNew();
List<Vector3Int> closeVertices = BFS(SelectedVertex, 10); // second param is the search depth
watch.Stop();
Debug.Log(watch.ElapsedMilliseconds);
foreach (var vert in closeVertices)
{
var index = (vert.y * (Width * Width)) + (vert.z * Width) + vert.x;
if (index >= Vertices.Length) continue;
Gizmos.color = Color.red;
Gizmos.DrawSphere(Vertices[index], 0.1f);
}
}
private List<Vector3Int> BFS(Vector3 start, int depth)
{
Vector3Int startIndex = new((int)(start.x / precision), (int)(start.y / precision), (int)(start.z / precision));
Dictionary<Vector3Int, bool> closedList = new();
List<Vector3Int> queue = new() { startIndex };
while (queue.Count > 0)
{
Vector3Int v = queue[^1];
queue.RemoveAt(queue.Count-1);
Vector3Int[] neighbors = new[]
{
v + Vector3Int.left,
v + Vector3Int.right,
v + Vector3Int.up,
v + Vector3Int.down,
v + Vector3Int.forward,
v + Vector3Int.back,
};
foreach (Vector3Int n in neighbors)
{
if (n.x < 0 || n.y < 0 || n.z < 0) continue; // this will alos include the high limit of the grid but i dont "need" it at this point of the tests
//For every implementation of graph search algorithms I make, this always seem to bee the weak part.
if ((n - startIndex).sqrMagnitude > depth*depth || queue.Any(vert => vert == n) || closedList.ContainsKey(n)) continue;
queue.Insert(0, n);
}
closedList.Add(v, true);
}
return closedList.Keys.ToList();
}
}
The use of a Dictionary for the closedList is a poor attempt at trying to reduce the List searching time, it used to be closedList.Any(vert => vert == n) but I didn't see great change by the use of each.
I'd be really glad if somebody could pinpoint what really slow this down.
Second question : How do you even MultiThread with BFS ? Both the queue and the closed list are very dynamic, is there a solution to work this out with NativeLists ?
Thank you for your time. Let me know if anything's unclear.
Upfront, this may, or may not be suitable in your situation, but as an exercise for myself as much as anything, I thought it'd be interesting to see how fast I could get the execution of the given code.
I created three methods in my test.
The first was your method verbatim.
The second method was your code, but 'rejigged' but taking the
neighbours array creation out of the loop and adding in a
Queue<Vector3Int> instead of the List<Vector3Int> as I suspected
that the insertion to element 0 of the List<T> might be a big part of the issue in your original code also replacing your Dictionary<Vector3Int, bool> with a HashSet<Vector3Int>. I also replaced Any(v => v == n) with Contains(v). Contains turned out to be significantly faster.
Then the third method I created, I went even further to add a second HashSet to enable a fast lookup of items that were still in the queue.
I then ran the tests against each method using a StopWatch to record the times. I also ran the last method again, but that time from Task.Run.
Here are the two 'optimised' methods:
private List<Vector3Int> BFS_Optimised ( Vector3 start, int depth )
{
Vector3Int startIndex = new((int)(start.x / precision), (int)(start.y / precision), (int)(start.z / precision));
HashSet<Vector3Int> closedList = new ();
Queue<Vector3Int> queue = new ();
queue.Enqueue(startIndex);
var dSquared = depth * depth;
Vector3Int[] neighbors =
new[] { Vector3Int.left, Vector3Int.right, Vector3Int.up, Vector3Int.down, Vector3Int.forward, Vector3Int.back };
while ( queue.Count > 0 )
{
var v = queue.Dequeue();
for ( int i = 0; i < 6; ++i )
{
var n = v + neighbors[i];
if ( n.x < 0 || n.y < 0 || n.z < 0 )
continue; // this will alos include the high limit of the grid but i dont "need" it at this point of the tests
if ( ( n - startIndex ).sqrMagnitude > dSquared
|| closedList.Contains ( n )
|| queue.Contains ( n ) ) // queue.Any(v => v == n ) ) //
continue;
queue.Enqueue ( n );
}
closedList.Add ( v );
}
return closedList.ToList ( );
}
private List<Vector3Int> BFS_Optimised2 ( Vector3 start, int depth )
{
Vector3Int startIndex = new((int)(start.x / precision), (int)(start.y / precision), (int)(start.z / precision));
HashSet<Vector3Int> closedList = new ();
Queue<Vector3Int> queue = new ();
queue.Enqueue ( startIndex );
HashSet<Vector3Int> qHash = new ( ) { startIndex };
var dSquared = depth * depth;
Vector3Int[] neighbors =
new[] { Vector3Int.left, Vector3Int.right, Vector3Int.up, Vector3Int.down, Vector3Int.forward, Vector3Int.back };
while ( queue.Count > 0 )
{
var v = queue.Dequeue();
qHash.Remove ( v );
for ( int i = 0; i < 6; i++ )
{
var n = v + neighbors[i];
if ( n.x < 0 || n.y < 0 || n.z < 0 )
continue; // this will alos include the high limit of the grid but i dont "need" it at this point of the tests
if ( ( n - startIndex ).sqrMagnitude > dSquared
|| closedList.Contains ( n )
|| qHash.Contains ( n ) )
continue;
queue.Enqueue ( n );
qHash.Add ( n );
}
closedList.Add ( v );
}
return closedList.ToList ( );
}
Here's the test (excuse the crudeness and lack of depth in tests):
async void Run ( )
{
var iterations = 100;
var d = 10;
var v = new Vector3 ( 10, 10, 10 );
List<Vector3Int> r1 = default;
List<Vector3Int> r2 = default;
List<Vector3Int> r3 = default;
List<Vector3Int> r4 = default;
Debug.Log ( "Waiting ... " );
await Task.Delay ( 2000 );
Debug.Log ( "Run ... " );
Stopwatch sw = new();
sw.Start ( );
for ( int i = 0; i < iterations; i++ )
r1 = BFS ( v, d );
sw.Stop ( );
var t1 = sw.Elapsed.TotalMilliseconds;
sw.Restart ( );
for ( int i = 0; i < iterations; i++ )
r2 = BFS_Optimised ( v, d );
sw.Stop ( );
var t2 = sw.Elapsed.TotalMilliseconds;
sw.Restart ( );
for ( int i = 0; i < iterations; i++ )
r3 = BFS_Optimised2 ( v, d );
sw.Stop ( );
var t3 = sw.Elapsed.TotalMilliseconds;
sw.Restart ( );
r4 = await Task.Run ( ( ) => BFS_Optimised2 ( v, d ) );
sw.Stop ( );
var t4 = sw.Elapsed.TotalMilliseconds;
StringBuilder sb = new();
sb.AppendLine ( $"Original : {t1} ms [{r1.Count}] ({r1 [ 0 ]}) .. ({r1 [ ^1 ]})" );
sb.AppendLine ( $"Optimised : {t2} ms [{r2.Count}] ({r2 [ 0 ]}) .. ({r2 [ ^1 ]})" );
sb.AppendLine ( $"Optimised2 : {t3} ms [{r3.Count}] ({r3 [ 0 ]}) .. ({r3 [ ^1 ]})" );
sb.AppendLine ( $"Optimised2 Task.Run : {t4} ms [{r4.Count}] ({r4 [ 0 ]}) .. ({r4 [ ^1 ]})" );
Debug.Log ( sb.ToString ( ) );
}
And here are the results:
Original : 10701.7465 ms [4169] ((10, 10, 10)) .. ((15, 5, 3))
Optimised : 1830.9519 ms [4169] ((10, 10, 10)) .. ((15, 5, 3))
Optimised2 : 209.1559 ms [4169] ((10, 10, 10)) .. ((15, 5, 3))
Optimised2 Task.Run : 17.7353 ms [4169] ((10, 10, 10)) .. ((15, 5, 3))
I cannot make out at a glance what your code is doing ( there is no docs and my C# is rusty ) But your BFS has an surprising amount of code ( A BFS usually only takes a few lines ). Why do you need to create a new, large data structure on EVERY pass through the while loop? That is expensive, and as far as I recall C# uses some bedeviled garbage collector thingy that is going to be fighting you all the way
You have a variable named queue, but it is not a queue but rather a list. This is confusing, and probably incorrect.
As far as I can make out, you are creating the neighbors of a node on the fly as you go along. Terrible idea! You should model the graph either with an adjacency matrix ( simplest ) or node adjacency lists ( more complicated, but memory efficient for large graphs ) Then you can move through the graph using the node indices to look up the neighbours without needing to create them over and over again.
For comparison, here is a C++ code for a BFS that uses a real queue and concerns itself only with node indices.
void cPathFinder::breadth(
std::function<void(int v, int p)> visitor)
{
if (!nodeCount())
throw std::runtime_error("breadth called on empty graph");
std::vector<bool> visited(nodeCount(), false);
std::queue<int> Q;
visited[myStart] = true;
Q.push(myStart);
while (Q.size())
{
int v = Q.front();
Q.pop();
for (int w : adjacent(v))
{
if (!visited[w])
{
// reached a new node
visitor(w, v);
visited[w] = true;
Q.push(w);
}
}
}
}
I'm using C# and I used a 2d array of structs for a grid of tiles.This is not about how to find 8 neighboring tiles from a tile in the grid. I understand that in c# you can have a series of yield returns make a ienumerable. Like:
public IEnumerable<int> fakeList()
{
yield return 1;
yield return 2;
}
And call it with a foreach loop. Now, in my grid class want to have an easy way to access neighbours in grid.array[x,y] and modify it. But since it is a struct, I can't write an iterator like:
public IEnumerable<int> neighbours(int x, int y)
{
if((x+1) >=0 && y >=0 && .....)//check if node above is inside grid
yield return grid.array[x+1,y];
//rinse and repeat 7 more times for each direction
}
Instead, every time I need the neighbors, I need to copy paste the 8if conditions and check that I'm using the correct x+direction,y+direction to find valid indices. Basically, a huge pain.
I could work around by:
Not using structs and making my life easier. And getting rid of possible premature optimization. BUT I'm going to running this code every frame in my game. Possibly multiple times. So I'd like to keep the structs if possible.
Write iterator for indices instead. Ex:
Is the 2nd approach valid? Or does it generate garbage? I don't know how yield return works in detail.
public struct GridTile
{
public int x;
public int z;
public GridTile(int x, int z)
{
this.x = x;
this.z = z;
}
}
public IEnumerable<int> neighbours(int x, int y)
{
if ((x + 1) >= 0 && y >= 0 && .....)//check if right node is inside
yield return new Gridtile(x + 1, y);
//rinse and repeat 7 more times for each direction
}
If you know the coordinates of a 2D array entry, then the neighbors can be retrieved using loops:
var twoD = new int[10,10];
var someX = 5;
var someY = 5;
List<int> neighbors = new List<int>();
for(int nx = -1; nx <= 1; nx++){
for(int ny = -1; ny <= 1; ny++){
int iX = someX + nX;
int iY = someY + nY;
if(iX > 0 && iX < twoD.GetLength(0) && iY > 0 && iY < twoD.GetLength(1))
neighbors.Add(twoD[iX,iY]);
}
}
Requirement: Iterate through a sorted list of strings, adding a char at the beginning of each string, then re-sorting. This may need to be done a few thousand times. I tried using a regular List of strings but, as expected, the process was way too slow.
I was going to try a List of StringBuilders but there is no direct way to sort the list. Any workarounds come to mind?
You've stated you can't sort a Link - however, you can if you can supply your own sort comparison:
List<StringBuilder> strings = new List<StringBuilder>();
// ...
strings.Sort((s1, s2) => s1.ToString().CompareTo(s2.ToString()));
The problem here as #phoog notes, is that in order to do so it allocates a lot of extra strings and isn't very efficient. The sort he provides is better. What we can do to figure out which approach is better is supply a test. You can see the fiddle here: http://dotnetfiddle.net/Px4fys
The fiddle uses very few strings and very few iterations because it's in a fiddle and there's a memory limit. If you paste this into a console app and run in Release you'll find there's huge differences. As #phoog also suggests LinkedList<char> wins hands-down. StringBuilder is the slowest.
If we bump up the values and run in Release mode:
const int NumStrings= 1000;
const int NumIterations= 1500;
We'll find the results:
List<StringBuilder> - Elapsed Milliseconds: 27,678
List<string> - Elapsed Milliseconds: 2,932
LinkedList<char> - Elapsed Milliseconds: 912
EDIT 2: When I bumped both values up to 3000 and 3000
List<StringBuilder> - Elapsed Milliseconds: // Had to comment out - was taking several minutes
List<string> - Elapsed Milliseconds: 45,928
LinkedList<char> - Elapsed Milliseconds: 6,823
The string builders will be a bit quicker than strings, but still slow, since you have to copy the entire buffer to add a character at the beginning.
You can create a custom comparison method (or comparer object if you prefer) and pass it to the List.Sort method:
int CompareStringBuilders(StringBuilder a, StringBuilder b)
{
for (int i = 0; i < a.Length && i < b.Length; i++)
{
var comparison = a[i].CompareTo(b[i]);
if (comparison != 0)
return comparison;
}
return a.Length.CompareTo(b.Length);
}
Invoke it like this:
var list = new List<StringBuilder>();
//...
list.Sort(CompareStringBuilders);
You would probably do better to look for a different solution to your problem, however.
Linked lists offer quick prepending, so how about using LinkedList<char>? This might not work if you need other StringBuilder functions, of course.
StringBuilder was rewritten for .NET 4, so I've struck out my earlier comments about slow prepending of characters. If performance is an issue, you should test to see where the problems actually lie.
Thanks to all for the suggestions posted. I checked these, and I have to say that I'm astonished that LinkedList works incredibly well, except for memory usage.
Another surprise is the slow sorting speed of the StringBuilder list. It works quickly as expected for the char insert phase. But the posted benchmarks above reflect what I've found: StringBuilder sorts very slowly for some reason. Painfully slow.
List of strings sorts faster. But counter to intuition, List of LinkedList sorts very fast. I have no idea how navigating a linked list could possibly be faster than simple indexing of a buffer (as in strings and StringBuilder), but it is. I would never have thought to try LinkedList. Compliments to McAden for the insight!
But unfortunately, LinkedList runs the system out of RAM. So, back to the drawing board.
Sort the StringBuilders as described in Phoog's answer, but keep the strings in reverse order in the StringBuilder instances - this way, you can optimize the "prepending" of each new character by appending it to the end of the StringBuilder's current value:
Update: with test program
class Program
{
static readonly Random _rng = new Random();
static void Main(string[] args)
{
int stringCount = 2500;
int initialStringSize = 100;
int maxRng = 4;
int numberOfPrepends = 2500;
int iterations = 5;
Console.WriteLine( "String Count: {0}; # of Prepends: {1}; # of Unique Chars: {2}", stringCount, numberOfPrepends, maxRng );
var startingStrings = new List<string>();
for( int i = 0; i < stringCount; ++i )
{
var sb = new StringBuilder( initialStringSize );
for( int j = 0; j < initialStringSize; ++j )
{
sb.Append( _rng.Next( 0, maxRng ) );
}
startingStrings.Add( sb.ToString() );
}
for( int i = 0; i < iterations; ++i )
{
TestUsingStringBuilderAppendWithReversedStrings( startingStrings, maxRng, numberOfPrepends );
TestUsingStringBuilderPrepend( startingStrings, maxRng, numberOfPrepends );
}
var input = Console.ReadLine();
}
private static void TestUsingStringBuilderAppendWithReversedStrings( IEnumerable<string> startingStrings, int maxRng, int numberOfPrepends )
{
var builders = new List<StringBuilder>();
var start = DateTime.Now;
foreach( var str in startingStrings )
{
builders.Add( new StringBuilder( str ).Reverse() );
}
for( int i = 0; i < numberOfPrepends; ++i )
{
foreach( var sb in builders )
{
sb.Append( _rng.Next( 0, maxRng ) );
}
builders.Sort( ( x, y ) =>
{
var comparison = 0;
var xOffset = x.Length;
var yOffset = y.Length;
while( 0 < xOffset && 0 < yOffset && 0 == comparison )
{
--xOffset;
--yOffset;
comparison = x[ xOffset ].CompareTo( y[ yOffset ] );
}
if( 0 != comparison )
{
return comparison;
}
return xOffset.CompareTo( yOffset );
} );
}
builders.ForEach( sb => sb.Reverse() );
var end = DateTime.Now;
Console.WriteLine( "StringBuilder Reverse Append - Total Milliseconds: {0}", end.Subtract( start ).TotalMilliseconds );
}
private static void TestUsingStringBuilderPrepend( IEnumerable<string> startingStrings, int maxRng, int numberOfPrepends )
{
var builders = new List<StringBuilder>();
var start = DateTime.Now;
foreach( var str in startingStrings )
{
builders.Add( new StringBuilder( str ) );
}
for( int i = 0; i < numberOfPrepends; ++i )
{
foreach( var sb in builders )
{
sb.Insert( 0, _rng.Next( 0, maxRng ) );
}
builders.Sort( ( x, y ) =>
{
var comparison = 0;
for( int offset = 0; offset < x.Length && offset < y.Length && 0 == comparison; ++offset )
{
comparison = x[ offset ].CompareTo( y[ offset ] );
}
if( 0 != comparison )
{
return comparison;
}
return x.Length.CompareTo( y.Length );
} );
}
var end = DateTime.Now;
Console.WriteLine( "StringBulder Prepend - Total Milliseconds: {0}", end.Subtract( start ).TotalMilliseconds );
}
}
public static class Extensions
{
public static StringBuilder Reverse( this StringBuilder stringBuilder )
{
var endOffset = stringBuilder.Length - 1;
char a;
for( int beginOffset = 0; beginOffset < endOffset; ++beginOffset, --endOffset )
{
a = stringBuilder[ beginOffset ];
stringBuilder[ beginOffset ] = stringBuilder[ endOffset ];
stringBuilder[ endOffset ] = a;
}
return stringBuilder;
}
}
results:
2500 strings initially at 100 characters, 2500 prepends:
Small bit of background first. I am developing a system that generates a "route" between locations. Locations have a pre-defined list of neighbours not limited to those adjacent to it. The search can safely assume that by picking the closest neighbour (numerically) to the target destination, it is making the optimal move towards it.
I have working code as shown below:
public Route GetRoute(int StartPoint, int Destination)
{
Route returnRoute = new Route();
returnRoute.steps = new List<int>();
bool locationReached = false;
int selectedNeighbour;
int distanceFromTarget;
int currentPoint = StartPoint; // set the current point to the start point
while (!locationReached)
{
selectedNeighbour = 0;
distanceFromTarget = 5000; // nominal amount guaranteed to be overwritten
var neighbours = locations.FirstOrDefault(l => l.LocationID == currentPoint).Neighbours;
for (int i = 0; i < neighbours.Length; i++)
{
// get the current neighbours, then check proximity
int currentNeighbour = neighbours[i];
int tempDistance = Math.Abs( currentNeighbour - Destination );
// if nearer than previous neighbour, set it as the chosen location
if ( tempDistance < distanceFromTarget )
{
distanceFromTarget = tempDistance;
selectedNeighbour = currentNeighbour;
// if the selected neighbour is the destination, we're done
if ( selectedNeighbour == Destination )
locationReached = true;
}
} // for
// add the selected neighbour if we found one
if ( selectedNeighbour != 0 )
{
currentPoint = selectedNeighbour;
returnRoute.steps.Add(selectedNeighbour);
}
else
{
Debug.Log ("No Route Found");
return returnRoute;
}
} // while
return returnRoute;
}
My question is regarding the loop of the neighbours (int[]) variable. How can this best be optimised? I've seen some use of linq and ordering, but also comments that this approach might be inefficient. I need efficiency over neatness here.
Many thanks.
.net 4
vs2010
winform
c#
added some points using
chart1.Series[0].Points.AddXY(x,y);
when I click on the chart, the cursor may not fall on any points.
Are there any functions to return the nearest point? (forget y, just x distance.)
Or I have to write my own binary search function?
private void Chart_MouseClick(object sender, MouseButtonEventArgs e)
{
LineSeries line = (LineSeries)mychart.Series[0];
Point point = e.GetPosition(line);
Int32? selectIndex = FindNearestPointIndex(line.Points, point);
// ...
}
private Int32? FindNearestPointIndex(PointCollection points, Point point)
{
if ((points == null || (points.Count == 0))
return null;
Func<Point, Point, Double> getLength = (p1, p2) => Math.Sqrt(Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.Y - p2.Y, 2)); // C^2 = A^2 + B^2
List<Points> results = points.Select((p,i) => new { Point = p, Length = getLength(p, point), Index = i }).ToList();
Int32 minLength = results.Min(i => i.Length);
return results.First(i => (i.Length == minLength)).Index;
}
To find the nearest point in a set of unordered points, you have to iterate through them all and keep track of the minimum distance. This has a time complexity of O(n).
You could significantly improve this by maintaining the points in a more organized data structure (such as an R-tree). There are third-party libraries available if you'd rather not implement your own. Many databases already support the R-tree for spatial indices.
If you really want to only search for the point with the nearest X-coordinate, this could be further simplified by storing the points in a sorted collection (such as a SortedList<TKey, TValue>) and performing a binary search (which SortedList<TKey, TValue>.IndexOfKey already implements).
/*My Fuzzy Binary Search*/
private int FindNearestId(System.Windows.Forms.DataVisualization.Charting.DataPointCollection p, uint ClickedX)
{
int ret = 0;
int low = 0;
int high = p.Count - 1;
bool bLoop = true;
while (bLoop)
{
ret = (low + high) / 2;
switch (FindNearestId_Match(p, ClickedX, ret))
{
case 0:
high = ret+1;
break;
case 1:
bLoop = false;
break;
case 2:
low = ret-1;
break;
}
}
return ret+1;
}
private int FindNearestId_Match(System.Windows.Forms.DataVisualization.Charting.DataPointCollection p, uint ClickedX, int id)
{
uint id0 = Convert.ToUInt32(p[id].XValue);
uint id1 = Convert.ToUInt32(p[id+1].XValue);
if ( (id0 <= ClickedX) && (ClickedX < id1) )
{
return 1;
}
else if ((id0 < ClickedX) && (ClickedX > id1))
{
return 2;
}
else
{
return 0;
}
}
Soultion can be more clear.
( as above you should use log complexity for accessing item )
double x-values solution:
double FindNearestPointYValueInSeries( System::Windows::Forms::DataVisualization::Charting::Series ^pxSeries, double dSearchedPosition )
{
int i_min = 0;
int i_max = pxSeries->Points->Count - 1;
int i_mean = 0;
double d ;
if ( i_max < 0 ) // not defined - minimum one point required
return Double::NaN;
while ( i_min <= i_max )
{
i_mean = (i_max + i_min ) / 2; // index of compared value in series
d = pxSeries->Points[ i_mean ]->XValue; // compared value
if ( d > dSearchedPosition ) // greater - search in right part
i_max = i_mean - 1;
else if ( d < dSearchedPosition ) // lower - search in left part
i_min = i_mean + 1;
else // equal ?
return d;
}
// delta is dSearchedPosition - pxSeries->Points[ i_mean ]->YValues[0]
// get Y value ( on index 0 )
return pxSeries->Points[ i_mean ]->YValues[0];
}