4
\$\begingroup\$

I developed a small package to do matrix math. I just learned Go so I may have done it wrong.

package matrix

import "fmt"
import "errors"
import "math"

type Matrix [][]float64

func Print(m Matrix) {
    fmt.Println(m)
}

func Zeros(rows, cols int) Matrix {
    m := make([][]float64, rows)
    for i := 0; i < rows; i++ {
        m[i] = make([]float64, cols)
    }
    return m
}

func Map(f func(float64) float64, m Matrix) Matrix {
    mapped := Zeros(len(m), len(m[0]))
    for i, row := range m {
        for j, val := range row {
            mapped[i][j] = f(val)
        }
    }
    return mapped
}

func Norm(m Matrix) float64 {
    var norm float64
    for _, row := range m {
        for _, val := range row {
            norm += math.Abs(val)
        }
    }
    return norm
}

func Transpose(m Matrix) Matrix {
    transposed := Zeros(len(m[0]), len(m))
    for i, row := range m {
        for j, val := range row {
            transposed[j][i] = val
        }
    }
    return transposed
}

func Multiply(a, b Matrix) (Matrix, error) {
    if len(a[0]) != len(b) {
        return nil, errors.New("Multiplication is not defined")
    }
    m := Zeros(len(a), len(b[0]))
    for i := 0; i < len(a); i++ {
        for j := 0; j < len(b[0]); j++ {
            for k := 0; k < len(b); k++ {
                m[i][j] += a[i][k] * b[k][j]
            }
        }
    }
    return m, nil
}

And here is a code using the package:

package main

import "matrix"

func main() {
    m := matrix.Map(func (f float64) float64 {
        return 1
    }, matrix.Zeros(2, 2))
    prod, _ := matrix.Multiply(m, m)
    println(matrix.Norm(prod))
}
\$\endgroup\$
2
\$\begingroup\$

In your code you often use the expressions len(m) and len(m[0]). The code would become clearer if you defined proper names for these expressions, like:

func (m Matrix) Rows() int { return len(m) }
func (m Matrix) Cols() int { return len(m[0]) }

This way, you can write zeros := matrix.Zeros(m.Rows(), m.Cols()), which make the code operate in mathematical terms instead of those of the implementing programming language. These additional methods won't affect the performance of your program since the compiler will generate equally efficient code for these method calls. You can verify this by looking at the generated assembly code using go tool objdump. And if there should really be a difference, file a bug report at https://github.com/golang/go/issues.

To see whether you defined a good API for your matrix package, you should write a corresponding unit test:

package matrix_test

import "testing"

func TestZeros(t *testing.T) {
    m := matrix.Zeros(3, 3)
    ...
}

Note that I wrote package matrix_test instead of the usual package matrix. This little change (which is also little-known) makes this test file a black-box test, which can only access the exported things from the matrix package.

At this point I thought that there were no way to access an individual element of the matrix. Therefore you should add another method:

func (m Matrix) At(row, col int) float64 { return m[row][col] }

Maybe it is possible to access the matrix elements using the [] operator, but I would not recommend this since you might want to implement the matrix later in a different way. Maybe you will mainly handle sparse matrices, where most of the elements are zero. Then you would need to change the implementation type to be more memory-efficient, and that change should not affect any code that already uses your package.

Another benefit of the At method is that it clearly defines that the ordering of the coordinates is row, col. I don't know whether mathematicians are always consistent about this little detail, so it's better to explicitly document it somewhere. Programmers definitely aren't consistent in this regard, not even after 60 years of confusion. There's (x, y), (top, left), (left, top), (height, width), (row, col), (col, row), and so on.

\$\endgroup\$
  • 1
    \$\begingroup\$ Wondeful! Yeah mathematicians always choose the row/col order :) \$\endgroup\$ – Blincer Jul 16 at 17:22
1
\$\begingroup\$

As for your multiply algorithm, it’s an \$n^3\$ algorithm. Look at some other implementations for a more efficient way to do this.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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