Golang DSA Chapter 5 Part 1: Arrays and Multi-Dimensional Arrays

Chapter 5 of “Learn Data Structures and Algorithms with Golang” by Bhagvan Kommadi shifts gears from trees and hash tables into something more math-heavy: homogeneous data structures. That basically means data structures where every element is the same type. Think arrays of integers, matrices of floats, that kind of thing.

If you’ve done any linear algebra or scientific computing, this chapter will feel familiar. If you haven’t, don’t worry. The ideas are simpler than they sound.

What Are Homogeneous Data Structures?

A homogeneous data structure is a collection where all elements share the same type. Integers, floats, doubles, whatever, but all the same. This is the foundation for matrices, vectors, and tensors.

Quick vocabulary check:

  • A scalar is just a single number. A zero-rank tensor, if you want to be fancy.
  • A vector is a row or column of numbers. A first-rank tensor.
  • A matrix is a two-dimensional grid of numbers.
  • A tensor is the general term for multi-dimensional arrays of numbers, used a lot in physics and machine learning.

The chapter focuses on two-dimensional and multi-dimensional arrays in Go, along with the standard matrix operations you’d expect.

Two-Dimensional Arrays in Go

We actually touched on 2D arrays back in Chapter 2, but now the book goes deeper. A 2D array is really just a list of 1D arrays. For dynamic allocation in Go, you’d use a slice of slices. But for fixed-size stuff, a regular 2D array works fine.

Here’s how you declare and initialize one:

var arr = [4][5]int{
    {4, 5, 7, 8, 9},
    {1, 2, 4, 5, 6},
    {9, 10, 11, 12, 14},
    {3, 5, 6, 8, 9},
}

That’s a 4x5 array. Four rows, five columns. To grab a specific element, you use two indices. Row first, then column:

var value int = arr[2][3]

That gives you the element at row 2, column 3, which is 12 in this case (remember, indexing starts at 0).

Traversing the whole thing takes O(m*n) time, where m is the number of rows and n is the number of columns. No shortcuts there, you just loop through every element.

The Order of a Matrix

The book introduces some terminology here. The “order” of a matrix is just its dimensions: m rows by n columns. A matrix with 3 rows and 4 columns is a 3x4 matrix.

A matrix can be multiplied by a scalar (just a single number). You can also divide a matrix by any non-zero number. These are basic operations, but they matter when you start doing linear algebra in code.

Types of Matrices

The chapter walks through several special matrix types. Let’s go through them.

Row Matrix

A row matrix has exactly one row. It’s a 1 x m matrix. Simple as that:

var matrix = [1][3]int{
    {1, 2, 3},
}

One row, three columns. That’s it.

Column Matrix

Flip it around. A column matrix has exactly one column. It’s an m x 1 matrix:

var matrix = [4][1]int{
    {1},
    {2},
    {3},
    {4},
}

Four rows, one column.

Lower Triangular Matrix

This one is more interesting. A lower triangular matrix has all zeros above the main diagonal. The diagonal goes from top-left to bottom-right. Everything above it is zero:

var matrix = [3][3]int{
    {1, 0, 0},
    {1, 1, 0},
    {2, 1, 1},
}

See the pattern? First row has values only in position [0][0]. Second row fills up to [1][1]. Third row fills everything. All the upper-right entries are zero.

Upper Triangular Matrix

The opposite. Everything below the diagonal is zero:

var matrix = [3][3]int{
    {1, 2, 3},
    {0, 1, 4},
    {0, 0, 1},
}

First row is full. Second row starts with a zero. Third row has two zeros. Mirror image of the lower triangular.

Null Matrix (Zero Matrix)

Exactly what it sounds like. Every single element is zero:

var matrix = [3][3]int{
    {0, 0, 0},
    {0, 0, 0},
    {0, 0, 0},
}

Not very exciting, but it’s the identity element for matrix addition (adding a zero matrix to any matrix gives you the original matrix back).

Identity Matrix

This is the matrix equivalent of the number 1. Ones on the main diagonal, zeros everywhere else. Multiplying any matrix by the identity matrix gives you the original matrix back.

The book shows a function that builds one dynamically:

func Identity(order int) [][]float64 {
    var matrix [][]float64
    matrix = make([][]float64, order)
    var i int
    for i = 0; i < order; i++ {
        var temp []float64
        temp = make([]float64, order)
        temp[i] = 1
        matrix[i] = temp
    }
    return matrix
}

Call Identity(4) and you get a 4x4 identity matrix. The trick is simple: for each row i, set only position i to 1. Everything else stays at the default zero value.

Symmetric Matrix

A symmetric matrix is one where the transpose equals itself. In other words, matrix[i][j] == matrix[j][i] for every pair of indices. The book mentions that symmetric matrices are a whole family, including things like covariance matrices, Hankel matrices, and skew-symmetric matrices. These come up a lot in statistics and physics.

Basic 2D Matrix Operations

Now we get to the fun part. The book walks through the core operations on 2x2 matrices. Let’s set up two matrices to work with:

var matrix1 = [2][2]int{
    {4, 5},
    {1, 2},
}
var matrix2 = [2][2]int{
    {6, 7},
    {3, 4},
}

Addition

Adding two matrices means adding corresponding elements. Position [0][0] of matrix1 plus position [0][0] of matrix2, and so on:

func add(matrix1 [2][2]int, matrix2 [2][2]int) [2][2]int {
    var m int
    var l int
    var sum [2][2]int
    for l = 0; l < 2; l++ {
        for m = 0; m < 2; m++ {
            sum[l][m] = matrix1[l][m] + matrix2[l][m]
        }
    }
    return sum
}

With our example matrices, adding them gives: {10, 12}, {4, 6}. Pretty straightforward.

Subtraction

Same idea, but you subtract instead:

func subtract(matrix1 [2][2]int, matrix2 [2][2]int) [2][2]int {
    var m int
    var l int
    var difference [2][2]int
    for l = 0; l < 2; l++ {
        for m = 0; m < 2; m++ {
            difference[l][m] = matrix1[l][m] - matrix2[l][m]
        }
    }
    return difference
}

Result: {-2, -2}, {-2, -2}.

Multiplication

This is where things get a little more involved. Matrix multiplication is not just multiplying corresponding elements. For each position in the result, you take the dot product of a row from the first matrix and a column from the second matrix:

func multiply(matrix1 [2][2]int, matrix2 [2][2]int) [2][2]int {
    var m int
    var l int
    var n int
    var product [2][2]int
    for l = 0; l < 2; l++ {
        for m = 0; m < 2; m++ {
            var productSum int = 0
            for n = 0; n < 2; n++ {
                productSum = productSum + matrix1[l][n]*matrix2[n][m]
            }
            product[l][m] = productSum
        }
    }
    return product
}

Notice the triple nested loop. That’s O(n^3) for an n x n matrix. For big matrices this gets expensive fast, which is why there are fancier algorithms (like Strassen’s) for large-scale matrix multiplication. But for 2x2, this works fine.

Transpose

Transposing a matrix means swapping rows and columns. Element at [i][j] moves to [j][i]:

func transpose(matrix1 [2][2]int) [2][2]int {
    var m int
    var l int
    var transMatrix [2][2]int
    for l = 0; l < 2; l++ {
        for m = 0; m < 2; m++ {
            transMatrix[l][m] = matrix1[m][l]
        }
    }
    return transMatrix
}

If your matrix looks like {4, 5}, {1, 2}, the transpose is {4, 1}, {5, 2}. Rows become columns and columns become rows.

Determinant

The determinant of a 2x2 matrix is a single number calculated as: (ad) - (bc), where the matrix is {a, b}, {c, d}:

func determinant(matrix1 [2][2]int) float32 {
    var det float32
    det = float32((matrix1[0][0] * matrix1[1][1]) - (matrix1[0][1] * matrix1[1][0]))
    return det
}

For our matrix1 {4, 5}, {1, 2}, the determinant is (42) - (51) = 3. The determinant tells you things like whether a matrix is invertible (if it’s zero, it’s not) and it shows up constantly in linear algebra.

Inverse

The inverse of a matrix is like division. If you multiply a matrix by its inverse, you get the identity matrix. For a 2x2 matrix, the formula uses the determinant:

func inverse(matrix [2][2]int) [][]float64 {
    var det float64
    det = determinant(matrix)
    var invmatrix [][]float64
    // swap diagonal, negate off-diagonal, divide by determinant
    invmatrix[0][0] = float64(matrix[1][1]) / det
    invmatrix[0][1] = -1 * float64(matrix[0][1]) / det
    invmatrix[1][0] = -1 * float64(matrix[1][0]) / det
    invmatrix[1][1] = float64(matrix[0][0]) / det
    return invmatrix
}

The key idea: you swap the diagonal elements, negate the off-diagonal elements, and divide everything by the determinant. If the determinant is zero, you can’t invert the matrix (it’s called a singular matrix).

What’s the Takeaway?

Chapter 5 Part 1 is really about building a vocabulary for working with arrays and matrices in Go. The matrix types (row, column, triangular, identity) are the building blocks. The operations (add, subtract, multiply, transpose, determinant, inverse) are the tools.

If you’re coming from Python with NumPy, all this stuff is one function call away. In Go, you’re doing it by hand. That’s actually a good thing for learning, because you see exactly what’s happening at each step. No magic.

In Part 2, we’ll look at some cooler matrix patterns: zig-zag matrices, spiral matrices, Boolean matrices, and multi-dimensional arrays (tensors). That’s where the chapter gets more creative.


Book: Learn Data Structures and Algorithms with Golang by Bhagvan Kommadi (Packt, 2019, ISBN: 978-1-78961-850-1)


PreviousChapter 4 Part 2: Hash Tables and Functions
NextChapter 5 Part 2: Matrix Operations in Go

About

About BookGrill.net

BookGrill.net is a technology book review site for developers, engineers, and anyone who builds things with code. We cover books on software engineering, AI and machine learning, cybersecurity, systems design, and the culture of technology.

Know More