I've written a Python script to generate the closest square spiral matrix to a given number.
I'm new to code reviews as a practice, and I'm interested in ways to improve. Please suggest improvements to the code where you see fit, particularly in regards to:
- Algorithm: Is there a faster/more elegant way to generate the matrix?
- Style: I've tried to adhere to PEP8.
"""
Outputs the closest square spiral matrix of an input number
"""
from math import sqrt
def is_even_square(num):
"""True if an integer is even as well as a perfect square"""
return sqrt(num).is_integer() and (num % 2 == 0)
def find_nearest_square(num):
"""Returns the nearest even perfect square to a given integer"""
for i in range(num):
if is_even_square(num - i):
return num - i
elif is_even_square(num + i):
return num + i
def find_lower_squares(num):
"""Returns a list of even perfect squares less than a given integer"""
squares = []
for i in range(num, 3, -1):
if is_even_square(i): squares.append(i)
return squares
def nth_row(num, n):
"""Returns the nth row of the square spiral matrix"""
edge = int(sqrt(num))
squares = find_lower_squares(num)
if n == 0:
return list(range(num, num - edge, -1))
elif n >= edge - 1:
return list(range(num - 3*edge + 3, num - 2*edge + 3))
elif n < edge // 2:
return ([squares[1] + n] + nth_row(squares[1],n-1)
+ [num - edge - n + 1])
else:
return ([num - 3*edge + 4 + n - edge] + nth_row(squares[1],n-1)
+ [num - 2*edge + 1 - n + edge])
def generate_square_spiral(num):
"""Generates a square spiral matrix from a given integer"""
edge = int(sqrt(num))
square_spiral = [[None for x in range(edge)] for y in range(edge)]
for row in range(edge): square_spiral[row] = nth_row(num, row)
return square_spiral
def main ():
num = None
while not num:
try:
num = int(input('Input number: '))
except ValueError:
print('Invalid Number')
nearest_square = find_nearest_square(num)
matrix = generate_square_spiral(nearest_square)
for row in range(len(matrix[0])):
for col in range(len(matrix)):
if matrix[row][col] < 10:
print(' ',matrix[row][col],' ',sep='',end='')
elif matrix[row][col] < 100:
print(' ',matrix[row][col],' ',sep='',end='')
else:
print(matrix[row][col],' ',sep='',end='')
print(2*"\n",end='')
if __name__ == '__main__':
main()
1 Answer 1
Your code is overall clean and well written.
You should use list comprehension:
def find_lower_squares(num):
"""Returns a list of even perfect squares less than a given integer"""
squares = []
for i in range(num, 3, -1):
if is_even_square(i): squares.append(i)
return squares
Should become:
def find_lower_squares(num):
"""Returns a list of even perfect squares less than a given integer"""
return [i for i in range(num, 3, -1) if is_even_square(i)]
Scientific/mathematical code like this greatly benefits from automated testing, let me show you an example:
import doctest
def find_lower_squares(num):
"""
Returns a list of even perfect squares less than a given integer
>>> find_lower_squares(40)
[36, 16, 4]
"""
return [i for i in range(num, 3, -1) if is_even_square(i)]
doctest.testmod()
This gives double benefits:
- Changes to the code can be made without being worried: if you break something, the error message will show up immediately
- Code readability and documentation increase a lot, the reader can see some examples of the function being used and will understand it faster and better.
Avoid None
-important assignment
In mid level languages such as C you must declare array first as empty and then fill them. In high level languages such as Python you should avoid such low level behaviour and insert the values in the arrays directly:
def generate_square_spiral(num):
"""
Generates a square spiral matrix from a given integer
>>> generate_square_spiral(5)
[[5, 4], [2, 3]]
>>> generate_square_spiral(17)
[[17, 16, 15, 14], [5, 4, 3, 13], [7, 1, 2, 12], [8, 9, 10, 11]]
"""
edge = int(sqrt(num))
square_spiral = [nth_row(num, row) for row in range(edge)]
return square_spiral
Also we could remove the unnecessary assignment:
edge = int(sqrt(num))
return [nth_row(num, row) for row in range(edge)]
-
1\$\begingroup\$ Both suggestion are extremely helpful. I didn't know about
doctest
. I'll start making a habit of using it \$\endgroup\$Tymric– Tymric2015年02月27日 23:03:56 +00:00Commented Feb 27, 2015 at 23:03 -
1\$\begingroup\$ @Timmy If you do so
int(sqrt(num))
will be computed each iteration of the loop, which is sub-optimal. \$\endgroup\$Caridorc– Caridorc2015年02月28日 17:00:52 +00:00Commented Feb 28, 2015 at 17:00 -
1\$\begingroup\$ @Timmy Yes, your solution is indeed preferable. \$\endgroup\$Caridorc– Caridorc2015年02月28日 17:05:57 +00:00Commented Feb 28, 2015 at 17:05