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)
| Previous | Chapter 4 Part 2: Hash Tables and Functions |
| Next | Chapter 5 Part 2: Matrix Operations in Go |