3
\$\begingroup\$

This generates a square matrix with spiral inward increasing values. How can I reduce this code?

Input Number = 3

Output

1 2 3

8 9 4

7 6 5

import math
row=int(input("Input nomber := "))
lis=[[0 for i in range(0,row)]for j in range(0,row)]
y=row
a=1
c=0
z=0
x=1
k=1
f=row/2
f=math.ceil(f)
for b in range(0,f):
    if b==k:
        row=row-1
        x=x+1
        z=z+1
        k=k+1
    for c in range(z,row):      
        lis[b][c]=a
        a=a+1
    for d in range(x,row):
        lis[d][row-1]=a
        a=a+1
    for e in range(row-1,z,-1):
        lis[row-1][e-1]=a
        a=a+1
    for f in range(row-2,z,-1):
        lis[f][z]=a
        a=a+1
for i in range(0,y):
    print()
    for j in range(0,y):
        print(lis[i][j],end="\t")
\$\endgroup\$
6
  • 1
    \$\begingroup\$ What does the code do? \$\endgroup\$
    – AJNeufeld
    Commented Dec 23, 2019 at 6:52
  • \$\begingroup\$ So an input of 3 produces a 3x3 matrix with the numbers 1 to 3². Is there anything significant about the ordering of the numbers? They don’t add up to a constant value, so it isn’t a magic square. At the risk of repeating myself, what does it do? Giving us an example of the output doesn’t tell us anything if we don’t understand what is significant about it. \$\endgroup\$
    – AJNeufeld
    Commented Dec 24, 2019 at 4:07
  • \$\begingroup\$ Nice piece of code, but quite unreadable, could you change the variable names to names which reflect the meaning of the variables. Editing the question before there are answers is ok. \$\endgroup\$
    – Jan Kuiken
    Commented Dec 24, 2019 at 10:43
  • \$\begingroup\$ @JanKuiken THANK YOU SIR \$\endgroup\$ Commented Dec 24, 2019 at 12:25
  • \$\begingroup\$ Unrelated to the question, but some of your suggested edits are getting rejected. Quite a lot of them, actually. Please be more careful when suggesting an edit. Please make sure you're not introducing new mistakes, and changing British to American spelling is not considered polite. Thank you. \$\endgroup\$
    – Mast
    Commented Mar 19, 2020 at 14:19

4 Answers 4

5
\$\begingroup\$

The code can be reduced by using:

row = int(input("Input number := "))
lis = [[0 for i in range(0,row)] for j in range(0,row)]
s = []
if row > 1:
    s += [row-1]
    for i in range(row-1, 0, -1):
        s += [i,i]
b = 1
e = 1
a = 0
c = 0
d = 0
lis[0][0] = e
for n in s:
    for f in range(n):
        c += a
        d += b
        e += 1
        lis[c][d] = e
    a, b = b, -a
for i in range(0,row):
    print()
    for j in range(0,row):
        print(lis[i][j],end="\t")

However this code is as unreadable as your code and probably uses a different method than yours. You do not only write code to perform a specific task, but you also want to communicate to other programmers (or yourself when you look at the code a year later) what you have done. This can be done by:

  • using sensible variable names
  • splitting up codes in smaller pieces (functions)
  • comments
  • docstrings

Your method is probably very clever, but I cannot figure out how it works from your code. My code can made be more understandable, although far from perfect, by applying previous points:

"""
Code to create a square matrix filled with ascending values in a inward
spiral starting from the upper left. The matrix is a list of lists.

Conventions used:
    i      - first matrix index or row index
    j      - second matrix index or column index
    di, dj - direction vector to move from one matrix position to another
"""

def rotate_90degrees_clockwise(di, dj):
    """Rotates a direction vector (di,dj) clockwise, i.e.:
        RIGHT(0,1) -> DOWN(1,0) -> LEFT(0,-1) -> UP(-1,0)
    """
    return dj, -di

def spiral_direction_steps(n):
    """Create a list of numbers of steps to go sequentially to the right, 
    down, left, up, right, down, left, up, ... etc.. to create a inward 
    spiraling route, starting from the upper left, i.e. for n = 3:
      2 x right, 2 x down, 2 x left, 1 x up, 1 x right
    General idea:
      1) first we go (n-1) x right, (n-1) x down, (n-1) x left
      2) then we go (n-2) x up, (n-2) x right
      3) then we go (n-3) x down, (n-3) x left
      4) repeat steps 2 and 3 till the number of steps is 1
    """
    retval = []
    if n > 1:
        retval += [n-1]
        for i in range(n-1, 0, -1):
            retval += [i,i]
    return retval

def spiral_matrix(n):
    """Generate a square matrix (list of lists) of size n x n, with ascending 
    numbers in a clockwise spiral, starting in the upper left corner
    """
    mat = [[0 for i in range(0,n)] for j in range(0,n)]

    val    = 1      # start value
    i, j   = 0, 0   # start point
    di, dj = 0, 1   # start direction 

    mat[i][j] = val # fill start point

    # fill other points
    steps =  spiral_direction_steps(n)
    for n in steps:
        for _ in range(n):
            i += di
            j += dj
            val += 1
            mat[i][j] = val
        di, dj = rotate_90degrees_clockwise(di, dj)
    return mat

def print_matrix(mat):
    """Prints a matrix which is a list of lists"""
    for row in mat:
        print()
        for col in row:
            print(col, end="\t")
    print()

def main():
    n = int(input("Input number := "))
    matrix = spiral_matrix(n)
    print_matrix(matrix)

if __name__ == "__main__":
    main()
\$\endgroup\$
6
+25
\$\begingroup\$

Covering what others suggested (e.g. @Srivaths), please follow at least PEP8 Python PEP8

Then, put your code in some function. Give meaningful names to variables. (What's a, c, x, z ... why are you not using b? )

You don't need import math -- instead of f=row/2 f=math.ceil(f), you can do f = row // 2 (assuming you use python 3).

Note that you can solve the problem more generally, for m x n matrix, which you can initialize as: matrix = [[0 for col in range(nCols)] for row in range(nRows)] (see the answer from StackOverflow, provided by @OK). This matrix = [[0] * m] * n, as pointed in the comments, won't work because of list references.

Now, we can also observe that you can fill the "outer rectangle", i.e. matrix[0][:] = range(1, nCols + 1); then, you can fill the rightmost column

cnt += nCols
for row in range(1, nRows):
  matrix[row][rightIdx] = cnt
  cnt += 1
# Bottom row:
matrix[bottomIdx][leftIdx:] = reversed(range(cnt, cnt + nCols - 1) # might be off by 1;
# Complete first column

# Put this in a while loop;

This problem is similar -- once you have the matrix, print it in a spiral order: Geeks for geeks website.

Also, you can check the solution of LeetCode Prob 54, but I would encourage you to try solving the problem yourself first. Problem 54 Solution for Problem 54

And here is my solution to Problem 54, similar to Solution 2 (Leetcode Solution link above): My solution :)

\$\endgroup\$
2
  • 1
    \$\begingroup\$ As the function used here is math.ceil(row / 2), I believe row // 2 will floor the parameter. (row + 1) // 2 should work fine though. \$\endgroup\$
    – Sriv
    Commented Feb 22, 2020 at 23:05
  • 1
    \$\begingroup\$ Using lis as [[0] * m] * n would create some weird errors. The reference to [0] * m will be created n times. If I modify any index, all the values in the column will also be modified. \$\endgroup\$
    – Sriv
    Commented Feb 22, 2020 at 23:07
5
\$\begingroup\$

As @JanKuiken mentioned, your idea is probably clever, but I can't understand what your code does either! Please add it to the question if possible!

  • You need more spaces in your code!

  • Prefer += and -= operators as they are more compact than assignments such as x = x + 1.

  • for variable in range(0, end) is not necessary as range starts the sequence with 0 by default.

  • Use meaningful variable names

  • The variable y is declared unnecessarily.

a = 1
c = 0
z = 0
x = 1
k = 1
  • The above part looks pretty bad. Change it to the below code
c = z = 0
a = x = k = 1
  • The variable f outside the for loop is conflicting with the f inside the for loop. You can remove the use of f with for b in range(math.ceil(row / 2)):

  • lis = [[0] * row for j in range(row)] is faster!

  • To print the array, use

for i in lis: # Faster and smaller!
    print(*i, sep='\t')

Here's a glimpse of how your final code might look like:

import math

row = int(input("Input number := "))

lis = [[0] * row for j in range(row)]

c = z = 0
a = x = k = 1

for b in range(math.ceil(row / 2)):
    if b == k:
        row -= 1
        x += 1
        z += 1
        k += 1

    for c in range(z, row):
        lis[b][c] = a
        a += 1

    for d in range(x, row):
        lis[d][row-1] = a
        a += 1

    for e in range(row-1, z, -1):
        lis[row-1][e-1] = a
        a += 1

    for f in range(row-2, z, -1):
        lis[f][z] = a
        a += 1

for i in lis:
    print(*i, sep='\t')

Here's how I'd have approached this problem:

n = int(input('Enter the size of the grid: '))
result = [[0] * n for _ in range(n)]

# Ending points
ei, ej = n // 2, (n - 1) // 2

# 0: RIGHT, 1: DOWN, 2: LEFT, 3: UP
orient = 0

def fill(i: int, j: int, di: int, dj: int, val: int) -> tuple:
    """
    'i' is the current row index
    'j' is the current column index
    'di' is the direction of the row (1: UP, -1: DOWN)
    'dj' is the direction of the column (1: RIGHT, -1: LEFT)
    'val' is the next value in the spiral
    """

    while 0 <= i + di < n and 0 <= j + dj < n:
        if result[i + di][j + dj] != 0:
            break

        i += di
        j += dj

        result[i][j] = val
        val += 1

    return i, j, val

# 'j' is -1 because the (0, 0) is yet to be filled
i, j = 0, -1
val = 1

while (i, j) != (ei, ej):
    if orient == 0: i, j, val = fill(i, j, 0, 1, val)
    if orient == 1: i, j, val = fill(i, j, 1, 0, val)
    if orient == 2: i, j, val = fill(i, j, 0, -1, val)
    if orient == 3: i, j, val = fill(i, j, -1, 0, val)

    orient = (orient + 1) % 4

for i in result:
    print(*i, sep='\t')
\$\endgroup\$
2
\$\begingroup\$

I try to resolve the problem using recursion. It's not the most efficient solution in Python, but it's elegant and clean.

def spiral(mx, i, j, dir, a, max_i, max_j):
    """
        mx: matrix to fill
        i, j: matrix position to analize
        dir: direction to fill
        a: list of values to insert
        max_i, max_j: dimension of matrix
    """
    # no more value tu insert
    if len(a) == 0:
        # stop recursion
        return

    if dir == "right":
        if j < max_j and mx[i][j] == 0:
            mx[i][j] = a[0]
            spiral(mx, i, j+1, "right", a[1:], max_i, max_i)
        else:
            spiral(mx, i+1, j-1, "down", a, max_i, max_j)
    elif dir == "down":
        if i < max_i and mx[i][j] == 0:
            mx[i][j] = a[0]
            spiral(mx, i+1, j, "down", a[1:], max_i, max_j)
        else:
            spiral(mx, i-1, j-1, "left", a, max_i, max_j)
    elif dir == "left":
        if j >= 0 and mx[i][j] == 0:
            mx[i][j] = a[0]
            spiral(mx, i, j-1, "left", a[1:], max_i, max_j)
        else:
            spiral(mx, i-1, j+1, "up", a, max_i, max_j)
    elif dir == "up":
        if i >= 0 and mx[i][j] == 0:
            mx[i][j] = a[0]
            spiral(mx, i-1, j, "up", a[1:], max_i, max_j)
        else:
            spiral(mx, i+1, j+1, "right", a, max_i, max_j)

# square matrix dimesion
n_dim = 30
# list of values to insert in matrix
l = [x+1 for x in range(n_dim**2)]
# matrix to fill
mx = [[0 for i in range(n_dim)] for j in range(n_dim)]

# start recursion
spiral(mx, 0, 0, "right", l, n_dim, n_dim)

for i in range(n_dim):
    for j in range(n_dim):
        print("{0:4d}".format(mx[i][j]), end="")
    print("\n")
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.