DEV Community

Shrijith Venkatramana
Shrijith Venkatramana

Posted on

Building a Row Echelon Form Checker in Python and Go with Tests

Row echelon form is a key concept in linear algebra for simplifying matrices during operations like Gaussian elimination. This post walks through creating a checker function to verify if a matrix is in row echelon form, following specific rules. We'll implement it in Python and Go, add test cases, and explore details with examples. The checker focuses on two main rules: all-zero rows must be at the bottom, and pivot positions must shift rightward in successive rows.

Understanding Row Echelon Form Basics

Row echelon form (REF) structures a matrix so each row starts with zeros followed by a non-zero pivot (if present), and pivots are staggered. This form helps in solving systems of equations by making back-substitution straightforward.

A matrix in REF looks like this:

[ 1, 2, 3 ]
[ 0, 1, 4 ]
[ 0, 0, 1 ]
Enter fullscreen mode Exit fullscreen mode

Here, pivots are in columns 1, 2, and 3. Not all REF matrices have pivots starting at 1; they can be any non-zero value, but often normalized to 1 in reduced REF (which we're not enforcing here).

For more on the formal definition, check the Wikipedia page on Row Echelon Form.

Key Rules for Our REF Checker

Our checker enforces these rules:

  • All-zero rows at the bottom: Any row with all zeros must appear after all non-zero rows.
  • Rightward pivot progression: The first non-zero element (pivot) in each row must be to the right of the pivot in the row above it.

We ignore whether pivots are 1 or if entries above pivots are zero—that's for reduced REF. Our focus is strict REF.

Consider this invalid example due to a misplaced all-zero row:

Row Elements
1 [0, 0, 0]
2 [0, 1, 2]
3 [0, 0, 3]

This fails the first rule. A valid version would move all-zero to bottom and then swap rows 1 and 2.

Implementing the Checker in Python

In Python, represent matrices as lists of lists. The function iterates through rows, tracks the last pivot column, and checks for all-zero rows out of place.

Here's the complete function:

def is_row_echelon_form(matrix):
    if not matrix or not matrix[0]:
        return True  # Empty matrix is trivially in REF

    rows = len(matrix)
    cols = len(matrix[0])

    # Ensure all rows have same number of columns
    for row in matrix:
        if len(row) != cols:
            return False

    last_pivot_col = -1
    seen_zero_row = False

    for i in range(rows):
        # Find the pivot position in this row
        pivot_col = -1
        for j in range(cols):
            if matrix[i][j] != 0:
                pivot_col = j
                break

        if pivot_col == -1:  # All-zero row
            seen_zero_row = True
            continue  # All-zero rows are okay if at bottom, checked later implicitly

        if seen_zero_row:
            return False  # Non-zero row after zero row

        if pivot_col <= last_pivot_col:
            return False  # Pivot not to the right

        last_pivot_col = pivot_col

    return True

# Example usage:
# matrix = [[1, 2, 3], [0, 1, 4], [0, 0, 1]]
# print(is_row_echelon_form(matrix))  # Output: True
Enter fullscreen mode Exit fullscreen mode

This code handles empty matrices and ensures uniform row lengths. It tracks last_pivot_col to enforce the pivot rule and seen_zero_row for zero placement.

Testing the Python Implementation

Test cases cover valid REF, invalid pivot positions, misplaced zeros, and edge cases like single-row or empty matrices.

Use Python's unittest for structured tests. Here's a complete test script:

import unittest

class TestRowEchelonForm(unittest.TestCase):
    def test_valid_ref(self):
        matrix = [[1, 2, 3], [0, 1, 4], [0, 0, 1]]
        self.assertTrue(is_row_echelon_form(matrix))

    def test_invalid_pivot_position(self):
        matrix = [[1, 2, 3], [1, 1, 4], [0, 0, 1]]  # Second row pivot not rightward
        self.assertFalse(is_row_echelon_form(matrix))

    def test_misplaced_zero_row(self):
        matrix = [[0, 0, 0], [0, 1, 2], [0, 0, 3]]
        self.assertFalse(is_row_echelon_form(matrix))

    def test_all_zero_rows(self):
        matrix = [[0, 0], [0, 0]]
        self.assertTrue(is_row_echelon_form(matrix))

    def test_empty_matrix(self):
        matrix = []
        self.assertTrue(is_row_echelon_form(matrix))

    def test_single_row(self):
        matrix = [[1, 2, 3]]
        self.assertTrue(is_row_echelon_form(matrix))

    def test_uneven_rows(self):
        matrix = [[1, 2], [0, 1, 3]]  # Uneven lengths
        self.assertFalse(is_row_echelon_form(matrix))

if __name__ == '__main__':
    unittest.main()

# When run, all tests should pass except the invalid ones, which correctly fail.
Enter fullscreen mode Exit fullscreen mode

These tests ensure the function behaves as expected. Run this script to verify—expect 7 tests, with no failures if implemented correctly.

For Python testing docs, see unittest in Python docs.

Porting the Checker to Go

Go uses slices of slices for matrices. The logic mirrors Python but with Go's type system and error handling for uneven rows.

Here's the full function in a main package:

package main

import "fmt"

func isRowEchelonForm(matrix [][]int) bool {
    if len(matrix) == 0 || len(matrix[0]) == 0 {
        return true // Empty matrix is in REF
    }

    rows := len(matrix)
    cols := len(matrix[0])

    // Check all rows have same columns
    for _, row := range matrix {
        if len(row) != cols {
            return false
        }
    }

    lastPivotCol := -1
    seenZeroRow := false

    for i := 0; i < rows; i++ {
        // Find pivot column
        pivotCol := -1
        for j := 0; j < cols; j++ {
            if matrix[i][j] != 0 {
                pivotCol = j
                break
            }
        }

        if pivotCol == -1 { // All-zero row
            seenZeroRow = true
            continue
        }

        if seenZeroRow {
            return false // Non-zero after zero
        }

        if pivotCol <= lastPivotCol {
            return false // Not rightward
        }

        lastPivotCol = pivotCol
    }

    return true
}

func main() {
    matrix := [][]int{{1, 2, 3}, {0, 1, 4}, {0, 0, 1}}
    fmt.Println(isRowEchelonForm(matrix)) // Output: true
}
Enter fullscreen mode Exit fullscreen mode

This version uses integers for simplicity; adjust to floats if needed for real matrices. Compile and run with go run main.go.

Testing the Go Implementation

Go's testing package handles tests. We'll mirror Python's cases.

Create a main_test.go file:

package main

import "testing"

func TestIsRowEchelonForm(t *testing.T) {
    tests := []struct {
        name    string
        matrix  [][]int
        want    bool
    }{
        {"valid_ref", [][]int{{1, 2, 3}, {0, 1, 4}, {0, 0, 1}}, true},
        {"invalid_pivot", [][]int{{1, 2, 3}, {1, 1, 4}, {0, 0, 1}}, false},
        {"misplaced_zero", [][]int{{0, 0, 0}, {0, 1, 2}, {0, 0, 3}}, false},
        {"all_zeros", [][]int{{0, 0}, {0, 0}}, true},
        {"empty", [][]int{}, true},
        {"single_row", [][]int{{1, 2, 3}}, true},
        {"uneven_rows", [][]int{{1, 2}, {0, 1, 3}}, false},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := isRowEchelonForm(tt.matrix); got != tt.want {
                t.Errorf("isRowEchelonForm() = %v, want %v", got, tt.want)
            }
        })
    }
}

// Run with 'go test' - expect all tests to pass.
Enter fullscreen mode Exit fullscreen mode

This table-driven test approach makes adding cases easy. Use go test to execute—look for "ok" output.

See Go's testing package in the official docs.

Comparing Python and Go Approaches

Python's dynamic typing simplifies matrix handling, but Go requires explicit checks for slice lengths. Both use similar loops, but Go's performance shines for large matrices due to compilation.

Aspect Python Go
Matrix Rep List of lists Slice of slices
Type Handling Flexible (int/float) Fixed (int here; use float64)
Error Check Runtime length check Runtime length check
Testing unittest class-based Table-driven with t.Run

Python feels quicker for prototyping, while Go suits production with stricter types.

Handling Edge Cases in Both Languages

Edge cases include non-square matrices, like [1,0],[0,0],[0,0] or [0,1],[1,0].

In code, our implementations handle these: non-square works as long as rows uniform. For floats, modify comparisons (e.g., use epsilon for zero).

Test addition: A matrix with leading zeros in rows but correct pivots, like [[0,1,2],[0,0,3]]—valid, pivot cols 1 and 2.

Both languages' codes pass this without changes. For large matrices, consider efficiency; our O(rows * cols) is fine for most uses.

Building this checker reinforces linear algebra basics and cross-language skills. Adapt it for reduced REF by adding checks for pivot=1 and upper zeros. Test thoroughly in your projects to catch matrix issues early.

Top comments (0)