HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Find diagonal positions for bishop movement

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
positionsbishopdiagonalmovementforfind

Problem

In a chess board, I need to find the diagonals a bishop can move on, and more specifically, the coordinates of those squares. So, given a grid of any size and a position in that grid (expressed in coordinates within the grid), I have to compute the coordinates of the diagonals of that initial position.

I'm using zero-based indexing, and the (row, column) notation for coordinates.

For example, on a 8x8 grid, with starting position of (0, 0), the returned list should be [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7)].

On a 8x8 grid, with starting position of (3, 4), the returned list should be [(3, 4), (2, 3), (1, 2), (0, 1), (4, 5), (5, 6), (6, 7), (4, 3), (5, 2), (6, 1), (7, 0), (2, 5), (1, 6), (0, 7)]

This is my working program in Python 3:

def diagonals(coord, size):
    limit = size - 1
    coords = [coord]
    row = coord[0]
    col = coord[1]

    while row > 0 and col > 0:
        row -= 1
        col -= 1
        coords.append((row, col))

    row = coord[0]
    col = coord[1]

    while row  0:
        row += 1
        col -= 1
        coords.append((row, col))

    row = coord[0]
    col = coord[1]

    while row > 0 and col < limit:
        row -= 1
        col += 1
        coords.append((row, col))

    return coords

coord = (3, 4)
size = 8
print(diagonals(coord, size))


Depending on the diagonal (4 cases), row and column are added or subtracted by one until the last square is reached, and everything is kept in a list, which in the end is returned.

It works, but it left me wondering if there's a simpler, different, better way of doing this, probably using linear algebra or something? And what about idiomatically, how can this be more pythonic?

Solution

The biggest problem you have is you have five lines that are nearly identical, four times.

row = coord[0]
col = coord[1]

while row > 0 and col > 0:
    row -= 1
    col -= 1


There is two ways to come at this. (1) make a function that yield the rows and columns. (2) use zip and range.

The first way you need to enter a start, limit and step for both the x and y axis.
You then need to limit all inputs by 0 < x < x_limit, and the same for the y axis too.
This is as we don't really know what direction we're going.
We then add the step to x each iteration.
This can make:

def _diagonal(x, y, x_limit, y_limit, x_diff, y_diff):
    while 0 < x < x_limit and 0 < y < y_limit:
        x += x_diff
        y += y_diff
        yield (x, y)


Where you'd use it like:

for x, y in _diagonal(row, col, limit, limit, 1, 1):
    coords.append((x, y))


However this is quite alike to range, we enter a start, limit and step in both.
Take range(row + 1, limit, 1).
To then get both x and y you need to use zip which works perfectly as it stops iterating when any of the inputs stop.
And so you can get:

for x, y in zip(range(row + 1, limit, 1), range(col + 1, limit, 1)):
    coords.append((x, y))


This I would say is better than creating your own function as you don't really have to implement anything yourself.

Either way you'll want to remove the duplicate code to append to coords.
To do this you can use itertools.chain.
This lets you chain iterators to become one iterator.
So since you want a list as output you'll have to 'wrap' the call in a list call.

This can get you:

from itertools import chain

def diagonals(coord, size):
    x, y = coord
    return list(chain(
        [(x, y)],
        zip(range(x - 1, -1, -1), range(y - 1, -1, -1)),
        zip(range(x + 1, size, 1), range(y + 1, size, 1)),
        zip(range(x + 1, size, 1), range(y - 1, -1, -1)),
        zip(range(x - 1, -1, -1), range(y + 1, size, 1)),
    ))

print(diagonals((3, 4), 8))

Code Snippets

row = coord[0]
col = coord[1]

while row > 0 and col > 0:
    row -= 1
    col -= 1
def _diagonal(x, y, x_limit, y_limit, x_diff, y_diff):
    while 0 < x < x_limit and 0 < y < y_limit:
        x += x_diff
        y += y_diff
        yield (x, y)
for x, y in _diagonal(row, col, limit, limit, 1, 1):
    coords.append((x, y))
for x, y in zip(range(row + 1, limit, 1), range(col + 1, limit, 1)):
    coords.append((x, y))
from itertools import chain

def diagonals(coord, size):
    x, y = coord
    return list(chain(
        [(x, y)],
        zip(range(x - 1, -1, -1), range(y - 1, -1, -1)),
        zip(range(x + 1, size, 1), range(y + 1, size, 1)),
        zip(range(x + 1, size, 1), range(y - 1, -1, -1)),
        zip(range(x - 1, -1, -1), range(y + 1, size, 1)),
    ))

print(diagonals((3, 4), 8))

Context

StackExchange Code Review Q#146935, answer score: 3

Revisions (0)

No revisions yet.