Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement Matrix , its Methods and Some Functions including Strassen Matrix Multiplication in Go #662

Merged
merged 20 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6a9aa2c
feat: Implement Strassen Matrix Multiplication
mohit07raghav19 Sep 30, 2023
b6fa85e
This commit introduces the Strassen matrix multiplication algorithm
mohit07raghav19 Oct 2, 2023
bcc7475
refactor: Rename functions and add implement new functionality
mohit07raghav19 Oct 3, 2023
400e75a
Implement Matrix as a struct type
mohit07raghav19 Oct 4, 2023
b1e4e01
Implement StrassenMatrixMultiply and Matrix Methods
mohit07raghav19 Oct 4, 2023
24fe1a6
Refactor to Use Value Type `Matrix` and Add Benchmarks
mohit07raghav19 Oct 5, 2023
7923f0b
Fixed Golang CI lint errors for file copy_test.go & submatrix_test.go
mohit07raghav19 Oct 6, 2023
f810d01
Merge branch 'TheAlgorithms:master' into master
mohit07raghav19 Oct 11, 2023
3fab688
refactor: Change type variable T to constraints.Integer, rename SameD…
mohit07raghav19 Oct 11, 2023
4d67ed9
refractor: Implement goroutines in Add, CheckEqual, Copy, New, Multip…
mohit07raghav19 Oct 11, 2023
910bb85
Merge branch 'TheAlgorithms:master' into master
mohit07raghav19 Oct 14, 2023
20bb9ee
refractor : Handled error in StrassenMatrixMultiply method instead of…
mohit07raghav19 Oct 14, 2023
dec410b
refractor : Handle errors gracefully by returning an error message in…
mohit07raghav19 Oct 16, 2023
a7e43d2
refractor : Updated the 'copy' function to return an empty matrix if …
mohit07raghav19 Oct 16, 2023
cabf07b
Merge branch 'TheAlgorithms:master' into master
mohit07raghav19 Oct 25, 2023
3c787c7
Updated Documentation in README.md
Oct 25, 2023
fc232f2
refactor: matrix operations to use context and sync packages
mohit07raghav19 Oct 25, 2023
382b49f
refactor: matrix operations to use context and sync packages
mohit07raghav19 Oct 25, 2023
51206f7
Updated Documentation in README.md
Oct 25, 2023
24dcb8f
chore: Add empty commit to trigger actions
mohit07raghav19 Oct 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module github.com/TheAlgorithms/Go

go 1.19

require golang.org/x/sync v0.4.0
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
52 changes: 52 additions & 0 deletions math/matrix/add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package matrix

import (
"context"
"errors"

"golang.org/x/sync/errgroup"
)

// Add adds two matrices.
func (m1 Matrix[T]) Add(m2 Matrix[T]) (Matrix[T], error) {
// Check if the matrices have the same dimensions.
if !m1.MatchDimensions(m2) {
return Matrix[T]{}, errors.New("matrices are not compatible for addition")
}

// Create a new matrix to store the result.
var zeroVal T
result := New(m1.Rows(), m1.Columns(), zeroVal)

ctx, cancel := context.WithCancel(context.Background())
defer cancel() // Make sure it's called to release resources even if no errors

var eg errgroup.Group
mohit07raghav19 marked this conversation as resolved.
Show resolved Hide resolved

for i := 0; i < m1.rows; i++ {
i := i // Capture the loop variable for the goroutine
eg.Go(func() error {
for j := 0; j < m1.columns; j++ {
select {
case <-ctx.Done():
return nil // Context canceled; return without an error
default:
}

sum := m1.elements[i][j] + m2.elements[i][j]
err := result.Set(i, j, sum)
if err != nil {
cancel() // Cancel the context on error
return err
}
}
return nil
})
}

if err := eg.Wait(); err != nil {
return Matrix[T]{}, err
}

return result, nil
}
58 changes: 58 additions & 0 deletions math/matrix/add_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package matrix_test

import (
"fmt"
"testing"

"github.com/TheAlgorithms/Go/math/matrix"
)

func TestAdd(t *testing.T) {
// Create two matrices with the same dimensions for addition
m1 := matrix.New(2, 2, 1)
m2 := matrix.New(2, 2, 2)

// Test case 1: Valid matrix addition
addedMatrix, err := m1.Add(m2)
if err != nil {
t.Errorf("Add(m1, m2) returned an error: %v, expected no error", err)
}
expectedMatrix := matrix.New(2, 2, 3)
res := addedMatrix.CheckEqual(expectedMatrix)
if !res {
t.Errorf("Add(m1, m2) returned incorrect result:\n%v\nExpected:\n%v", addedMatrix, expectedMatrix)
}

// Create two matrices with different dimensions for addition
m3 := matrix.New(2, 2, 1)
m4 := matrix.New(2, 3, 2)

// Test case 2: Matrices with different dimensions
_, err2 := m3.Add(m4)
expectedError2 := fmt.Errorf("matrices are not compatible for addition")
if err2 == nil || err2.Error() != expectedError2.Error() {
t.Errorf("Add(m3, m4) returned error: %v, expected error: %v", err2, expectedError2)
}

}

func BenchmarkAddSmallMatrix(b *testing.B) {
m1 := matrix.New(10, 10, 0) // Create a 10x10 matrix with all zeros
m2 := matrix.New(10, 10, 1) // Create a 10x10 matrix with all ones

for i := 0; i < b.N; i++ {
_, _ = m1.Add(m2)
}
}

func BenchmarkAddLargeMatrix(b *testing.B) {
size := 1000 // Choose an appropriate size for your large matrix
m1 := MakeRandomMatrix[int](size, size)
m2 := MakeRandomMatrix[int](size, size)

b.ResetTimer() // Reset the timer to exclude setup time

for i := 0; i < b.N; i++ {
_, _ = m1.Add(m2)
}
}
32 changes: 32 additions & 0 deletions math/matrix/checkequal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package matrix

// CheckEqual checks if the current matrix is equal to another matrix (m2).
// Two matrices are considered equal if they have the same dimensions and
// all their elements are equal.
func (m1 Matrix[T]) CheckEqual(m2 Matrix[T]) bool {
if !m1.MatchDimensions(m2) {
return false
}

c := make(chan bool)

for i := range m1.elements {
go func(i int) {
for j := range m1.elements[i] {
if m1.elements[i][j] != m2.elements[i][j] {
c <- false
return
}
}
c <- true
}(i)
}

for range m1.elements {
if !<-c {
return false
}
}

return true
}
62 changes: 62 additions & 0 deletions math/matrix/checkequal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package matrix_test

import (
"testing"

"github.com/TheAlgorithms/Go/math/matrix"
)

func TestCheckEqual(t *testing.T) {
// Create two matrices with the same dimensions and equal values
m1 := matrix.New(2, 2, 0)
m2 := matrix.New(2, 2, 0)

// Test case 1: Matrices are equal
equal := m1.CheckEqual(m2)

if !equal {
t.Errorf("CheckEqual(m1, m2) returned false, expected true (matrices are equal)")
}

// Create two matrices with the same dimensions but different values
m3 := matrix.New(2, 2, 1)
m4 := matrix.New(2, 2, 0)

// Test case 2: Matrices are not equal
equal2 := m3.CheckEqual(m4)
if equal2 {
t.Errorf("CheckEqual(m3, m4) returned true, expected false (matrices are not equal)")
}

// Create two matrices with different dimensions
m5 := matrix.New(2, 2, 0)
m6 := matrix.New(2, 3, 0)

// Test case 3: Matrices have different dimensions
equal3 := m5.CheckEqual(m6)

if equal3 {
t.Errorf("CheckEqual(m5, m6) returned true, expected false (matrices are not equal)")
}
}

func BenchmarkCheckEqualSmallMatrix(b *testing.B) {
m1 := matrix.New(10, 10, 0) // Create a 10x10 matrix with all zeros
m2 := matrix.New(10, 10, 0) // Create another 10x10 matrix with all zeros

for i := 0; i < b.N; i++ {
_ = m1.CheckEqual(m2)
}
}

func BenchmarkCheckEqualLargeMatrix(b *testing.B) {
size := 1000 // Choose an appropriate size for your large matrix
m1 := MakeRandomMatrix[int](size, size)
m2 := MakeRandomMatrix[int](size, size)

b.ResetTimer() // Reset the timer to exclude setup time

for i := 0; i < b.N; i++ {
_ = m1.CheckEqual(m2)
}
}
40 changes: 40 additions & 0 deletions math/matrix/copy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package matrix

import "sync"

func (m Matrix[T]) Copy() (Matrix[T], error) {
rows := m.Rows()
columns := m.Columns()
zeroVal, _ := m.Get(0, 0) // Get the zero value of the element type

copyMatrix := New(rows, columns, zeroVal)
var wg sync.WaitGroup
wg.Add(rows)
errChan := make(chan error, 1)

for i := 0; i < rows; i++ {
go func(i int) {
defer wg.Done()
for j := 0; j < columns; j++ {
val, _ := m.Get(i, j)
err := copyMatrix.Set(i, j, val)
if err != nil {
select {
case errChan <- err:
default:
}
return
}
}
}(i)
}

wg.Wait()
close(errChan)

if err, ok := <-errChan; ok {
return Matrix[T]{}, err
}

return copyMatrix, nil
}
100 changes: 100 additions & 0 deletions math/matrix/copy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package matrix_test

import (
"testing"

"github.com/TheAlgorithms/Go/math/matrix"
)

func TestMatrixCopy(t *testing.T) {
// Create a sample matrix
data := [][]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}
// Ensure that the copy is not the same as the original
matrix, _ := matrix.NewFromElements(data)
copyMatrix, err := matrix.Copy()
if err != nil {
t.Fatalf("Failed to copy matrix: %v", err)
}

// Ensure that the copy is not the same as the original
if &matrix == &copyMatrix {
t.Errorf("Copy did not create a new matrix.")
}

for i := 0; i < matrix.Rows(); i++ {
for j := 0; j < matrix.Columns(); j++ {
val1, _ := matrix.Get(i, j)
val2, _ := copyMatrix.Get(i, j)
if val1 != val2 {
t.Errorf("Copy did not correctly copy element (%d, %d).", i, j)
}
}
}

}

func TestMatrixCopyEmpty(t *testing.T) {
// Create an empty matrix
emptyMatrix := matrix.New(0, 0, 0)

// Make a copy of the empty matrix
copyMatrix, err := emptyMatrix.Copy()
if err != nil {
t.Fatalf("Failed to copy matrix: %v", err)
}

// Ensure that the copy is not the same as the original by comparing their addresses
if &emptyMatrix == &copyMatrix {
t.Errorf("Copy did not create a new matrix for an empty matrix.")
}

// Check if the copy is also empty
if copyMatrix.Rows() != 0 || copyMatrix.Columns() != 0 {
t.Errorf("Copy of an empty matrix should also be empty.")
}
}

func TestMatrixCopyWithDefaultValues(t *testing.T) {
// Create a matrix with default values (zeroes)
rows, columns := 3, 3
defaultValue := 0
defaultMatrix := matrix.New(rows, columns, defaultValue)

// Make a copy of the matrix
copyMatrix, err := defaultMatrix.Copy()
if err != nil {
t.Fatalf("Failed to copy matrix: %v", err)
}

// Ensure that the copy is not the same as the original by comparing their addresses
if &defaultMatrix == &copyMatrix {
t.Errorf("Copy did not create a new matrix for default values.")
}

// Check if the copy has the same values as the original (all zeroes)
for i := 0; i < defaultMatrix.Rows(); i++ {
for j := 0; j < defaultMatrix.Columns(); j++ {
val1, _ := defaultMatrix.Get(i, j)
val2, _ := copyMatrix.Get(i, j)
if val1 != val2 || val1 != defaultValue || val2 != defaultValue {
t.Errorf("Copy did not preserve default values at row %d, column %d. Expected %v, got %v", i, j, defaultValue, val2)
}
}
}
}

func BenchmarkCopyMatrix(b *testing.B) {
// Create a matrix for benchmarking
rows := 100
columns := 100
initialValue := 0
matrix := matrix.New(rows, columns, initialValue)

// Reset the benchmark timer
b.ResetTimer()

// Run the benchmarks
for i := 0; i < b.N; i++ {
_, _ = matrix.Copy()
}
}
17 changes: 17 additions & 0 deletions math/matrix/isvalid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package matrix

import "github.com/TheAlgorithms/Go/constraints"

// IsValid checks if the input matrix has consistent row lengths.
func IsValid[T constraints.Integer](elements [][]T) bool {
if len(elements) == 0 {
return true
}
columns := len(elements[0])
for _, row := range elements {
if len(row) != columns {
return false
}
}
return true
}
Loading