My program generates the following list (excerpt):
my_list = [{'x': 1764, 'y': 18320, 'class': 'note', 'id': 'd1e2443'},
{'x': 1764, 'y': 20030, 'class': 'note', 'id': 'd1e2591'},
{'x': 1807, 'y': 12650, 'class': 'note', 'id': 'd1e1362'},
{'x': 2243, 'y': 20120, 'class': 'note', 'id': 'd1e2609'},
{'x': 2243, 'y': 22685, 'class': 'note', 'id': 'd1e2769'},
{'x': 2257, 'y': 12560, 'class': 'note', 'id': 'd1e1380'},
{'x': 2688, 'y': 20210, 'class': 'note', 'id': 'd1e2625'},
{'x': 2707, 'y': 10040, 'class': 'note', 'id': 'd1e1194'},
{'x': 2707, 'y': 12650, 'class': 'note', 'id': 'd1e1398'},
{'x': 2707, 'y': 14720, 'class': 'note', 'id': 'd1e1571'},
{'x': 2901, 'y': 18140, 'class': 'note', 'id': 'd1e2475'}]
It is already sorted by the value of the 'x'-key. I am trying to write a method, that returns a tuple of two elements of this list for a given coordinate (xPos, yPos)
:
x <= xPos
)x > xPos
)The distance is simply the euclidean distance ("Pythagoras"). A second parameter for the function is the maximum distance allowed:
def getNearest(noteList, posX, posY, maxDistance):
[...]
return leftElement, rightElement
I have tried to use the bisect function to get the insertion point of the closest element to xPos
as well as for xPos - maxDistance
(case 'left') and xPos + maxDistance
(case 'right) respectively in order to narrow down the search area. Then I calculated the distance for every remaining element in this sliced list
Somehow this feels very unelegant. Is there any better way of doing this?
EDIT: Maybe I was not very clear with my intention: I need two elements of the list. The nearest element in the '2D pane' to the left and one to the right. Thus I need to consider the y-coordinate as well.
It might happen (acutally almost every time) that the closest element in regard to its x-coordinate is way more far away than an element with a close-by y-coordinate.
That seems like a good solution, but from the way you described it, I don't see a need to do more than a single bisect.
bisect_left
already returns the index of the element such that your first condition is satisfied (x <= xPos - maxDistance
). From there, I'd simply iterate the elements to the right one by one until you reach x > xPos + maxDistance
.
This will probably yield better performance considering the CPU cache, because you're iterating adjacent positions instead of jumping around
If you start processing a large number of points or maxDistance
, this algorithm will probably not suffice. In that case, you should look into using a kd-tree, as wenzul suggested.
you could use min()
to find the nearest elements on both sides of posX
, in regards to their Euclidean distance.
>>>import math
>>>def getNearest(noteList, posX, posY):
... leftElement = min([i for i in my_list if i['x'] <= posX], key=lambda x:abs(math.sqrt((x['x']- posX)**2+(x['y']- posY)**2)))
... rightElement = min([i for i in my_list if i['x'] > posX], key=lambda x:abs(math.sqrt((x['x']- posX)**2+(x['y']- posY)**2)))
... return (leftElement, rightElement)
>>> getNearest(my_list, 2000, 2000)
({'y': 12650, 'x': 1807, 'class': 'note', 'id': 'd1e1362'}, {'y': 10040, 'x': 2707, 'class': 'note', 'id': 'd1e1194'})
>>> getNearest(my_list, 2000, 20000)
({'y': 20030, 'x': 1764, 'class': 'note', 'id': 'd1e2591'}, {'y': 20120, 'x': 2243, 'class': 'note', 'id': 'd1e2609'})
Where the Key
is the Euclidean distance between each element on the 2D pane, and the element passed to the function, ie (posX, PosY)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With