Multiply
P
G
// multiply.go
// description: Implementation of matrix multiplication
// time complexity: O(n^3) where n is the number of rows in the first matrix
// space complexity: O(n^2) where n is the number of rows in the first matrix
package matrix
import (
"context"
"errors"
"sync"
)
// Multiply multiplies the current matrix (m1) with another matrix (m2) and returns the result as a new matrix.
func (m1 Matrix[T]) Multiply(m2 Matrix[T]) (Matrix[T], error) {
// Check if the matrices can be multiplied.
if m1.Columns() != m2.Rows() {
return Matrix[T]{}, errors.New("matrices cannot be multiplied: column count of the first matrix must match row count of the second matrix")
}
// Create a new matrix to store the result.
var zeroVal T
result := New(m1.Rows(), m2.Columns(), zeroVal)
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // Make sure it's called to release resources even if no errors
var wg sync.WaitGroup
errCh := make(chan error, 1)
for i := 0; i < m1.Rows(); i++ {
for j := 0; j < m2.Columns(); j++ {
i, j := i, j // Capture the loop variable for the goroutine
wg.Add(1)
go func() {
defer wg.Done()
// Compute the dot product of the row from the first matrix and the column from the second matrix.
dotProduct := zeroVal
for k := 0; k < m1.Columns(); k++ {
select {
case <-ctx.Done():
return // Context canceled; return without an error
default:
}
val1, err := m1.Get(i, k)
if err != nil {
cancel()
select {
case errCh <- err:
default:
}
return
}
val2, err := m2.Get(k, j)
if err != nil {
cancel()
select {
case errCh <- err:
default:
}
return
}
dotProduct += val1 * val2
}
err := result.Set(i, j, dotProduct)
if err != nil {
cancel()
select {
case errCh <- err:
default:
}
return
}
}()
}
}
// Wait for all goroutines to finish
go func() {
wg.Wait()
close(errCh)
}()
// Check for any errors
if err := <-errCh; err != nil {
return Matrix[T]{}, err
}
return result, nil
}