3

I am wondering what's the best solution for this. Return and show the nearest hyperlink on a webpage when the mouse is clicked.

There are 3 DIVs in this example. each of them carries a hyperlink inside. There's also another hyperlink (hyperlink D) by itself without a DIV. And lets say the red dot is mouse click.

For Example

The solution I can think of is just iterate through all the links by doing

    var a_list = document.getElementsByTagName("a");

and then compute the distance by using distance equation c^2 = a^2 + b^2, so simply

    var a_list = Array.prototype.slice.call(document.getElementsByTagName("a"))
    for( var i = 0 ; i < a_list.length; i++){
          Math.sqrt(Math.pow(mouseX - a_list[i].getBoundingClientRect().left,2) + 
                    Math.pow(mouseY - a_list[i].getBoundingClientRect().top,2))

    }

This approach certainly takes about O(N) time complexity where N is the number of links that we have. Can we do better ?

peter
  • 8,333
  • 17
  • 71
  • 94
  • Shouldn't it be `Math.sqrt(Math.pow(mouseX - a_list[i].getBoundingClientRect().left,2) + Math.pow(mouseY - a_list[i].getBoundingClientRect().top,2))` instead of `Math.sqrt(Math.abs(mouseX - a_list[i].getBoundingClientRect().left) + Math.abs(mouseY - a_list[i].getBoundingClientRect().top))`? – Oriol Sep 01 '12 at 18:57
  • I guess there is more to that, than hits the eye! Assume the click goes to the left lower corner of your sketch - would C or D be the answer you want? Or would you prefer "nothing"? What about a click that is aequidistant from B and C, but inside DIV C? – Eugen Rieck Sep 01 '12 at 18:58
  • You are right I mistyped. The question has been updated – peter Sep 01 '12 at 18:59
  • @Eugen Rieck if there's a tie, then it doesn't really matter, but lets say return to left one – peter Sep 01 '12 at 19:01

4 Answers4

2

It's not possible to do this faster than O(N) because you do need to check each link.

I can think of faster algorithms if you were doing this check lots of times, (e.g. if you were highlighting the closest link every time the mouse moved); but those have one-off start-up costs that are worse than a simple check.

Does it really matter that it's an O(N) algorithm? Isn't it fast enough?

Some notes on your algorithm:

  • You can eliminate the expensive square root computation - you can compare "distance squared" values instead, it'll still tell you what's closer. If you do really need the distance for something, take the square root once at the end, after you've found the closest link
  • You need to check which corner of the link is closest to the mouse, don't blindly use the left corner. I.e. for X direction:
    • if mouseX < link.left then use (mouseX - link.left)^2
    • if mouseX > link.right then use (mouseX - link.right)^2
    • else use 0
    • ... and similarly for Y direction.
  • You could calculate delta_y^2 first and compare that against your "best distance^2 so far" value; if it's greater then there's no need to calculate delta_x^2. Depending on browser, CPU and the rest of your code, this may or may not make it faster.
user9876
  • 10,954
  • 6
  • 44
  • 66
  • It doesn't have to faster than O(n). I am just wondering other possibilities. so by saying 'distance squared' you mean (x1-x2)^2 + (y1-y2)^2 without taking the square root ? and can you explain your 3rd bullet point please – peter Sep 01 '12 at 19:39
  • In fact it can be solved in **O(log N)** (see comment to my answer below) – Ridcully Sep 01 '12 at 19:53
  • Ridcully: That algorithm has a setup step which is much slower than O(N). If you're only doing one search, the total time will be higher. If you're doing lots of searches, then that algorithm would be better. – user9876 Sep 01 '12 at 22:03
  • @user1389813: Yes, I mean (x1-x2)^2 + (y1-y2)^2 without taking the square root. As for the 3rd point... suppose we have the mouse clicked at (0,0) and our anchors are (50,50), (0,100), and (51, 51). The first anchor gives us `(y1-y2)^2 + (x1-x2)^2==5000`. For the 2nd one, we calculate the `(y1-y2)^2` bit and get `10000`... that's bigger than `5000` so we can move on straight away, we don't need to calculate the `(x1-x2)^2` bit. The 3rd one gives us `(y1-y2)^2==2601` so we have to continue calculating `(y1-y2)^2 + (x1-x2)^2==5202`; that's `>5000` so we know the first anchor was closest. – user9876 Sep 01 '12 at 22:10
2

This looks like a nearest neighbor questions like you have a set S of n points in 2 dimensions, and a query point q. Which point in S is the closest one to q.

I think if you are dealing with not that many links (less than hundred links), then simple approach is the best (the one that you just have. Scan through each of them and calculate the distance, and take the minimal one), however if thousand or millions of links (barely happen) then you definitely need to cache it for later querying purposes, and that certainly speed up the time for querying.

  • One way (your way) is to get all the links and then sort them by X coordinate, you don't care about Y coordinate just for now, and then do a binary search on X, and you will end up with one of the link, but that may not be the closest one, you still need to compare with the previous one and the next one by distance (now use Y coordinate). For instance, From your example, when you do a binary search on X since the X coordinate of that red dotted (mouse clicked position) is greater than hyperlink D, so it will search everything after that, but hyperlink D could be the closest, so you need to take into consideration. This approach takes O(N log N) for sorting, O(N) space, and O(log N) for querying.

  • You can also use K-d Tree. It works pretty well with small number of dimensions (in your case it's only two dimensions x and y), and it can efficiently search for the cell containing the query point p (your mouse clicked position).
    Construction time O(N log N), Space O(N) and Query Time: O(n^1/2 + k)

  • Another way I can think of is to construct the Voronoi diagram, which provide an efficient data structure for nearestneighbor queries and best for two dimension.
    Construction time O(N log N), Space O(N) and Query Time: O(log n)

So pretty much all the approaches are the same.

garriual
  • 71
  • 4
  • So looks like sorting is necessary for most of the cases – peter Sep 02 '12 at 16:15
  • Yes, unless your a_list is already sorted, but as far as i know when you document.getElementsByTagName("a") doesn't give you the sorted order. – garriual Sep 02 '12 at 16:18
1

If you can build your link list beforehand (i.e. no scrolling or rebuild on scroll), you might want to take a look at tiling.

E.g. for a tiling factor of 0.5 you would categorize the links in being

  • in the left 0.25 of the screen
  • in the left 0.5 of the screen
  • in 0.25-0.75 left of the screen
  • in 0.5-1.0 left (=0.5 right) of the screen
  • in 0.75-1.0 left (=0.25 right) of the screen

The same goes for vertical.

On a click, you only need to check the links in those tiles, that overlap the click location. This will obviously give you "nothing" if you are very far from the nearest link, but this could be what you want.

Eugen Rieck
  • 64,175
  • 10
  • 70
  • 92
  • That's the solution I was thinking of. In the case where it doesn't find a link in the tile, you can default to the O(N) comparison of all links, but most of the time it will be faster. But I suggest that this is premature optimization -- first check whether the O(N) solution is fast enough before you get complicated. – Barmar Sep 01 '12 at 19:31
  • What do you mean by tiling ? can you explain more in detail ? is it like a matrix and every time you just check which bucket does it fall into based on mouseX and mouseY ? – peter Sep 01 '12 at 19:44
  • Exactly. Each tile is an array of all the anchors in that section of the screen. Notice that Eugen Rieck's answer has overlapping tiles, to solve this problem: when an anchor is near the edge of a tile, and you click just on the other side of that edge, that anchor may still be the closest one even though it's in another tile. – Barmar Sep 02 '12 at 16:55
1

So, I guess you have that one page and do the calculation for a lot of different mouse-positions?

In that case, you could, in a first setup make 2 lists holding the as sorted in x, resp. y direction by it's left, resp. top values. Then with your mouse-event's x resp. y coordinate you can start searching in these lists like so - explained for x coordinate:

Begin with complete list of as and compare mouse-x with left-value of element in middle of list. If x is smaller, repeat but with list from beginning to middle element, if x == left-value, your done, if x is larger, repeat with list from middle to end. Do so if either x == left-value or the list to search is only 1 or two elements long in which case you take the nearer of the two.

The same has to be done (kind a parallel) for the y coordinate.

This is not completely thought through, but I'm quite sure with something like that, you can avoid to compare with all a tags every time.

Ridcully
  • 23,362
  • 7
  • 71
  • 86
  • So sort all the links by x and then do a binary search for querying on x ? and then similar to y ? is that what you were saying ? – peter Sep 01 '12 at 19:42
  • Basically yes, but you'll have to search somehow in parallel, because it might well be that you find a match in x direction where the y values are very different. – Ridcully Sep 01 '12 at 19:50
  • In fact this seems to be a quite famous question, addressed by Donald E. Knuth himself, and can be solved with **O(log N)**. Refer to this Wikipedia article: http://en.wikipedia.org/wiki/Nearest_neighbor_search – Ridcully Sep 01 '12 at 19:52