开发者

Ordering cells by their distance from center cell(s)

I had someone ask me, what at that time seemed an innocent enough question:

How do we ordering cells in a 2D array by their distance from predefined/precomputed center cell(s).

Here is a table showing how far the particular cell is from predefined center cells ( they have values o开发者_开发百科f 0 in them ). A value of n means it's n cells away from the center:

+----+----+----+----+----+----+----+----+
| 4  | 4  | 3  | 3  | 3  | 3  | 4  | 4  |
+----+----+----+----+----+----+----+----+
| 4  | 3  | 2  | 2  | 2  | 2  | 3  | 4  |
+----+----+----+----+----+----+----+----+
| 3  | 2  | 1  | 1  | 1  | 1  | 2  | 3  |
+----+----+----+----+----+----+----+----+
| 3  | 2  | 1  | 0  | 0  | 1  | 2  | 3  |
+----+----+----+----+----+----+----+----+
| 3  | 2  | 1  | 0  | 0  | 1  | 2  | 3  |
+----+----+----+----+----+----+----+----+
| 3  | 2  | 1  | 1  | 1  | 1  | 2  | 3  |
+----+----+----+----+----+----+----+----+
| 4  | 3  | 2  | 2  | 2  | 2  | 3  | 4  |
+----+----+----+----+----+----+----+----+
| 4  | 4  | 3  | 3  | 3  | 3  | 4  | 4  |
+----+----+----+----+----+----+----+----+

I solved the problem by computing the straight line distance between ( x1, y1 ) and ( x2, y2 ) in an Euclidean space and sorting them using the old school "Decorate-Sort-Undecorate" method.

This is what I ended up with:

import math
boardMaxRow = 8
boardMaxCol = 8
thatAbsurdLargeValue = ( 1 + boardMaxRow + boardMaxCol )
centerCells = ( ( 3, 3 ), ( 3, 4 ), ( 4, 3 ), ( 4, 4 ) )
cellsOrderedFromTheCenter = {}

for row in xrange( boardMaxRow ):
    for col in xrange( boardMaxCol ):
        minDistanceFromCenter = thatAbsurdLargeValue
        for ( centerX, centerY ) in centerCells:
            # straight line distance between ( x1, y1 ) and ( x2, y2 ) in an Euclidean space
            distanceFromCenter = int( 0.5 + math.sqrt( ( row - centerX ) ** 2 + ( col - centerY ) ** 2 ) )
            minDistanceFromCenter = min( minDistanceFromCenter, distanceFromCenter )
        cellsOrderedFromTheCenter[ ( row, col ) ] = minDistanceFromCenter

board = [ keyValue for keyValue in cellsOrderedFromTheCenter.items() ]

import operator

# sort the board in ascending order of distance from the center
board.sort( key = operator.itemgetter( 1 ) )
boardWithCellsOrderedFromTheCenter = [ key for ( key , Value ) in board ]
print boardWithCellsOrderedFromTheCenter

The output:

[(3, 3), (4, 4), (4, 3), (3, 4), (5, 4), (2, 5), (2, 2), (5, 3), (3, 2), (4, 5), (5, 5), (2, 3), (4, 2), (3, 5), (5, 2), (2, 4), (1, 3), (6, 4), (5, 6), (2, 6), (5, 1), (1, 2), (6, 3), (1, 5), (3, 6), (4, 1), (1, 4), (2, 1), (6, 5), (4, 6), (3, 1), (6, 2), (7, 3), (4, 7), (3, 0), (1, 6), (3, 7), (0, 3), (7, 2), (4, 0), (2, 0), (5, 7), (1, 1), (2, 7), (6, 6), (5, 0), (0, 4), (7, 5), (6, 1), (0, 2), (7, 4), (0, 5), (0, 7), (6, 7), (7, 6), (7, 7), (0, 0), (7, 1), (6, 0), (1, 0), (0, 1), (7, 0), (0, 6), (1, 7)]

I am amazed at how much code I got in there, for such a trivial problem.

My question is : can I make it faster and/or shorter ( use less temporaries/function calls )?


To make it shorter (and using a slightly different metric):

>>> rows, cols, centerx, centery = 6, 6, 2.5, 2.5
>>> [p[1:] for p in sorted((((x - centerx) ** 2 + (y - centery) ** 2, x, y)
...                         for x in xrange(rows) for y in xrange(cols)))]
[(2, 2), (2, 3), (3, 2), (3, 3), (1, 2), (1, 3), 
 (2, 1), (2, 4), (3, 1), (3, 4), (4, 2), (4, 3), 
 (1, 1), (1, 4), (4, 1), (4, 4), (0, 2), (0, 3),
 (2, 0), (2, 5), (3, 0), (3, 5), (5, 2), (5, 3), 
 (0, 1), (0, 4), (1, 0), (1, 5), (4, 0), (4, 5),
 (5, 1), (5, 4), (0, 0), (0, 5), (5, 0), (5, 5)]

To make it faster:

  • Don't take square roots (as in my code above): sorting by the square of the distance is just as good as sorting by the distance, and taking square roots is relatively slow unnecessary.
  • Exploit the 8-way symmetry: sort one octant and copy it out 8 times.

In the comments, PoorLuzer asks, "I also did not understand why you init centerx, centery = 2.5, 2.5." I hope this figure makes it clear:

Ordering cells by their distance from center cell(s)

PoorLuzer also wonders how come our metrics differ, given that we are both using the Euclidean distance formula. Well, my metric takes the distance from the centre of each square to the centre of the whole grid. For example, for these 8 cells the distance from the centre is √2.5 = about 1.58:

Ordering cells by their distance from center cell(s)

Whereas PoorLuzer is taking the Euclidean distance to the closest of the four centre squares (and rounding it to an integer). For the same 8 cells PoorLuzer assigns a distance of 1:

Ordering cells by their distance from center cell(s)


Shorter is easy:

coordinates = [(x,y) for y in range(boardMaxRow) 
                     for x in range(boardMaxCol)]

def dist(A,B):
    a,b = A
    c,d = B
    # real euklidian distance without rounding
    return (a-c)**2+(b-d)**2 

print list(sorted(coordinates, 
    key=lambda x: min(dist(x,c) for c in centerCells)))
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜