Search code examples
gopointersslicematrix-multiplication

Change 2D slice in-place


I am implementing a matrix-matrix multiplication algorithm in Go and I cannot reason how to change the output matrix in-place. I have tried changing the input to a pointer but 2D slices cannot be pointers?

package main

import (
    "fmt"
    "strconv"
    "math/rand"
    "os"
    "time"
)

func main() {
    L := len(os.Args)
    m, n, p, q, err := mapVars(L, os.Args)
    if err != 0 {
        fmt.Fprintf(os.Stderr, "error: Incorrect command line arguments.\n")
        os.Exit(1)
    }

    fmt.Println("The product array has dimensions.")
    fmt.Printf("\tC is %dx%d\n", m, q)

    fmt.Println("\nPopulating matrix A.")
    A, _ := createMat(m, n)
    fmt.Println("Matrix A.")
    printMat(m, A)

    fmt.Println("\nPopulating matrix B.")
    B, _ := createMat(p, q)
    fmt.Println("Matrix B.")
    printMat(p, B)

    fmt.Println("\nPerforming row-wise matrix-matrix multiplication AB.")
    startRow := time.Now()
    C := rowMultMat(m, n, q, A, B)
    dtRow := time.Since(startRow)
    fmt.Printf("Time elapsed: %v\n", dtRow)
    fmt.Println("Matrix C.")
    printMat(q, C)

}

func mapVars(l int, args []string) (m int, n int, p int, q int, err int) {
    if l == 2 {
        m, _ := strconv.Atoi(args[1])
        n, _ := strconv.Atoi(args[1])
        p, _ := strconv.Atoi(args[1])
        q, _ := strconv.Atoi(args[1])
        fmt.Printf("Creating two arrays, A, B, with square dimensions.\n")
        fmt.Printf("\tA is %dx%d\n\tB is %dx%d\n", m, n, p, q)
        return m, n, p, q, 0
    } else if (l == 5 || n != p) {
        m, _ := strconv.Atoi(args[1])
        n, _ := strconv.Atoi(args[2])
        p, _ := strconv.Atoi(args[3])
        q, _ := strconv.Atoi(args[4])
        fmt.Println("Creating two arrays, A, B, with dimensions.")
        fmt.Printf("\tA is %dx%d\n\tB is %dx%d\n", m, n, p, q)
        return m, n, p, q, 0
    } else {
        fmt.Println("Incorrect command line arguments.\n")
        return 0, 0, 0, 0, 1
    }   
}

func initMat(m int, n int) (M [][]float64, rows []float64) {
    M = make([][]float64, m)
    rows = make([]float64, n*m)
    for i := 0; i < m; i++ {
        M[i] = rows[i*n : (i+1)*n]
    }
    return M, rows
}

func createMat(m int, n int) (M [][]float64, rows []float64) {
    M = make([][]float64, m)
    rows = make([]float64, n*m)
    for i := 0; i < m; i++ {
        for j := 0; j < n; j++ {
            rows[i*n + j] = float64(rand.Int63()%10)
        }
        M[i] = rows[i*n : (i+1)*n]
    }
    return M, rows
}

func printMat(row int, M [][]float64) {
    for i := 0; i < row; i++ {
        fmt.Printf("%v\n", M[i])
    }
}

func rowMultMat(m int, n int, q int, A [][]float64, B [][]float64) (C [][]float64) {
    C, _ = initMat(m, q)
    var total float64 = 0.0
    for i := 0; i < m; i++ {
        for j := 0; j < q; j++ {
            for k := 0; k < n; k++ {
                total += A[i][k] * (B[k][j])
            }
            C[i][j] = total
            total = 0
        }
    }
    return C
}

Currently I am initializing the matrix inside rowMultMat because I am unable to pass C as a pointer to a 2D slice. For example, run main.go 2 3 3 2 will multiply a 2x3 with 3x2 to yield 2x2.


Solution

  • A slice is already a reference value. If you pass a slice into a function, the function can modify its contents (*) and the modifications will be visible to the caller once it returns.

    Alternatively, returning a new slice is also efficient - because again, slices are just references and don't take up much memory.


    (*) By contents here I mean the contents of the underlying array the slice points to. Some attributes like the slice's length cannot be changed in this way; if your function needs to make the slice longer, for example, you'll have to pass in a pointer to a slice.