1

I am solving a problem which involves traversing a n x m matrix in a zigzag fashion, where n and m can be same number or different numbers, i.e. we will have a square or rectangular matrix.

To solve this problem, I have a main method which will traverse the matrix; and within this main method, I create and call on moveDiagonallyUpwards and moveDiagonallyDownwards methods to move in the zigzag way.

The problem I am having is that when I pass row and col as arguments from the main method into these two other methods, and inside these methods I update row and col, e.g. +=1 or -= 1. These changes DO NOT reflect in the main method, when I return to the main method. And I do understand why this is the case.

So my challenge is, how can I pass back the updated row and col to the main method? I thought of using a global class to achieve the above. My idea is to create a separate class to hold these variables like the below, but I am having problems with calling and using these global row and col in my main method.

Any ideas how to adjust the row and col in main method to achieve the above? Thanks!

array = [
    [1, 3, 4, 10],
    [2, 5, 9, 11],
    [6, 8, 12, 15],
    [7, 13, 14, 16]
  ]

class matrixMovement: 
    def __init__(self,row=0,col=0,output=[]): 
        self.row = row
        self.col = col
        self.output = output
#Main Method
def zigzagTraverse(array):
    output.append(array[row][col])
    while array.row<= len(array)-1 or array.col<= len(array[0])-1: 
        if array.row< len(array)-1: 
            array.row += 1
            output.append(array[row][col])
            diagonalUp(row,col,array,output)
        if col < len(array[0])-1: 
            col += 1
            output.append(array[row][col])
            diagonalDown(row,col,array,output)
    return output
            
def diagonalUp(row,col,array,output): 
    while row > 0 and col< len(array[0])-1: 
        row -= 1
        col += 1
        output.append(array[row][col])
    return matrixMovemenet(row,col,output)
    
def diagonalDown(row,col,array,output): 
    while row<len(array)-1 and col > 0: 
        col-= 1
        row += 1
        output.append(array[row][col])
    return matrixMovemenet(row,col,output)
Patrick_Chong
  • 434
  • 2
  • 12

2 Answers2

1

You have a few problems. One is local scope. Another is using the return statement but doing nothing with the object returned.

There are a few ways to solve this. One is to ignore the local variables and just create an object from matrixMovement and utilize the fact that it's mutable.

class MatrixMovement: 
    def __init__(self, row=0, col=0, output=None):
        # I modified this to avoid the mutable default
        if output is None:
            output = []
        self.row = row
        self.col = col
        self.output = output


# Main Method
def zigzag_traverse(array):
    # this is the object we will be mutating
    obj = MatrixMovement()
    obj.output.append(array[obj.row][obj.col])
    while obj.row <= len(array) - 1 or obj.col <= len(array[0]) - 1: 
        if obj.row < len(array) - 1: 
            obj.row += 1
            obj.output.append(array[obj.row][obj.col])
            diagonal_up(obj, array)
        if obj.col < len(array[0]) - 1: 
            obj.col += 1
            obj.output.append(array[obj.row][obj.col])
            diagonal_down(obj, array)
        # without this condition the loop will never break
        if obj.row == len(array) - 1 and obj.col == len(array[0]) - 1:
            break
    return obj.output


def diagonal_up(obj, array):
    # since we are passing in a mutable object
    # anything attribute we change on it
    # will be reflected on the object from the call site
    while obj.row > 0 and obj.col < len(array[0]) - 1: 
        obj.row -= 1
        obj.col += 1
        obj.output.append(array[obj.row][obj.col])


def diagonal_down(obj, array):
    # the same rules as diagonal_up
    while obj.row < len(array) - 1 and obj.col > 0: 
        obj.col -= 1
        obj.row += 1
        obj.output.append(array[obj.row][obj.col])
Axe319
  • 4,255
  • 3
  • 15
  • 31
  • 1
    It's so neat! This is kind of how I thought about doing it, but I was thinking how to write matrixMovement like a million times throughout the code, so when you do obj= matrixMovement; this is so helpful! - as I didn't know you could do this. – Patrick_Chong Dec 22 '21 at 11:11
  • I was aware of the referencing too, I see how you referenced obj. everything, which makes. Can I ask- why did you do an if statement when initialising the output? can I not initialize it as output = [] in the argument? – Patrick_Chong Dec 22 '21 at 11:13
  • You can but if you call it with no arguments it will use the same `[]` for every subsequent call. See [this question](https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument/11416002) for a better explanation. – Axe319 Dec 22 '21 at 11:14
  • 1
    I should mention I didn't actually test if the base code itself has a bug. I just changed it to behave the way you expected yours to. – Axe319 Dec 22 '21 at 11:18
  • Okay, to make sure I understand it right from the link, by doing output = [] will be an issue- as stated in the link- only when we call it with no arguments? Otherwise it is fine? – Patrick_Chong Dec 22 '21 at 11:19
  • @Patrick_Chong that is correct. It's best to just leave it out or set it to something like `None` so users of the function don't assume that they can call it without arguments. – Axe319 Dec 22 '21 at 11:31
  • thanks for confirming @Axe319. One other thing- I am now debugging the code, and I noticed that when the debugger runs over obj.row or obj.col ; I am presented with a MatrixMovement object- is there anyway I can turn this into integer for the purpose of debugging? - As it's hard to tell where the issue is without this! – Patrick_Chong Dec 22 '21 at 13:16
-2

I chose to write this code with documentation, boundary checks, and after solving a few bugs I found in your traversal.

Explanation is written in comments. You're welcome to ask if there's anything you don't understand.

from typing import TypeVar, Sequence, TypeAlias

from dataclasses import dataclass

_T = TypeVar("_T")

MatrixType = Sequence[Sequence[_T]]

@dataclass
class Point:
    row: int
    col: int

def zigzag_traverse(matrix: MatrixType[_T]) -> list[_T]:
    output: list[_T] = []

    row_length = len(matrix[0])
    if any(len(row) != row_length for row in matrix):
        raise ValueError("Matrix is not rectangular.")
    
    # Initialize.
    boundry = Point(len(matrix) - 1, row_length - 1)
    
    if boundry.row < 0 or boundry.col < 0:
        return output  # Matrix is empty.

    pointer = Point(0, 0)


    # Start traversing.

    output.append(matrix[pointer.row][pointer.col])
    
    
    while pointer != boundry:

        # Go right until edge, then start moving down.
        if pointer.col < boundry.col:
            pointer.col += 1
        else:
            pointer.row += 1

        output.extend(_diagonal_left_down(pointer, matrix))

        # We reached the boundry, stop traversing.
        if pointer == boundry:
            break

        # Go down until edge, then start moving right.
        if pointer.row < boundry.row:
            pointer.row += 1
        else:
            pointer.col += 1

        output.extend(_diagonal_right_up(pointer, matrix))
    
    return output

def _diagonal_left_down(pointer: Point, matrix: MatrixType[_T]) -> list[_T]:
    """Traverse left-down diagonal.
    
    Args:
        pointer: The current position. Will be modified.
        matrix: The matrix to move over.
    
    Returns:
        The list of elements traversed.
    """
    row, col = pointer.row, pointer.col
    output = []

    while row < len(matrix) - 1 and col > 0:
        output.append(matrix[row][col])
        row += 1
        col -= 1
    
    # Reached the edge
    output.append(matrix[row][col])
    pointer.row, pointer.col = row, col

    return output

def _diagonal_right_up(pointer: Point, matrix: MatrixType[_T]) -> list[_T]:
    """Traverse right-up diagonal.
    
    Args:
        pointer: The current position. Will be modified.
        matrix: The matrix to move over.
    
    Returns:
        The list of elements traversed.
    """
    row, col = pointer.row, pointer.col
    output = []

    while row > 0 and col < len(matrix[0]) - 1:
        output.append(matrix[row][col])
        row -= 1
        col += 1
    
    # Reached the edge
    output.append(matrix[row][col])
    pointer.row, pointer.col = row, col

    return output
Bharel
  • 23,672
  • 5
  • 40
  • 80