Compare commits
	
		
			2 Commits
		
	
	
		
			13b2e664f3
			...
			7b8a6f03c2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 7b8a6f03c2 | |||
| 9729fe6dcb | 
| @@ -4,6 +4,7 @@ | ||||
| package matrix | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
|  | ||||
| 	"golang.org/x/exp/constraints" | ||||
| @@ -54,6 +55,33 @@ func Create[T Number](rows, cols int) *Matrix[T] { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Creates a new matrix from a given 2D slice of values. The first index of the | ||||
| // slice denotes the rows and the second index denotes the columns. Returns the | ||||
| // newly created matrix. | ||||
| // | ||||
| // Panics with ErrIncompatibleDataDimensions if any of the lengths are 0 or if | ||||
| // not all rows have the same length. | ||||
| func CreateFromSlice[T Number](values [][]T) *Matrix[T] { | ||||
| 	rows := len(values) | ||||
| 	if rows == 0 { | ||||
| 		panic(ErrInvalidDimensions) | ||||
| 	} | ||||
| 	cols := len(values[0]) | ||||
| 	if cols == 0 { | ||||
| 		panic(ErrInvalidDimensions) | ||||
| 	} | ||||
|  | ||||
| 	m := Create[T](rows, cols) | ||||
| 	for i := range rows { | ||||
| 		if len(values[i]) != cols { | ||||
| 			panic(ErrIncompatibleDataDimensions) | ||||
| 		} | ||||
| 		copy(m.values[i], values[i]) | ||||
| 	} | ||||
|  | ||||
| 	return m | ||||
| } | ||||
|  | ||||
| // Creates a new matrix of a given size and sets the values from the given | ||||
| // slice. The values are used to fill the matrix left to right and top to | ||||
| // bottom. Returns the newly created matrix. | ||||
| @@ -80,6 +108,17 @@ func CreateFromFlatSlice[T Number](rows, cols int, values []T) *Matrix[T] { | ||||
| 	return m | ||||
| } | ||||
|  | ||||
| // CreateFromJSON creates a new matrix from JSON data representing a | ||||
| // two-dimensional array. Returns an error if the JSON is invalid, the array is | ||||
| // empty, or the inner arrays have inconsistent lengths. | ||||
| func CreateFromJSON[T Number](data []byte) (*Matrix[T], error) { | ||||
| 	m := &Matrix[T]{} | ||||
| 	if err := m.UnmarshalJSON(data); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return m, nil | ||||
| } | ||||
|  | ||||
| // Convert will take a matrix of type T and convert it into a matrix of type U | ||||
| // Only works on SimpleNumber matrices | ||||
| func Convert[U, T SimpleNumber](in *Matrix[T]) *Matrix[U] { | ||||
| @@ -293,3 +332,39 @@ func (m *Matrix[T]) Fill(value T) *Matrix[T] { | ||||
| 	} | ||||
| 	return m | ||||
| } | ||||
|  | ||||
| // UnmarshalJSON implements json.Unmarshaler for Matrix, creating a matrix from | ||||
| // a JSON two-dimensional array. | ||||
| func (m *Matrix[T]) UnmarshalJSON(data []byte) error { | ||||
| 	var values [][]T | ||||
| 	if err := json.Unmarshal(data, &values); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	rows := len(values) | ||||
| 	if rows == 0 { | ||||
| 		return ErrInvalidDimensions | ||||
| 	} | ||||
| 	cols := len(values[0]) | ||||
| 	if cols == 0 { | ||||
| 		return ErrInvalidDimensions | ||||
| 	} | ||||
|  | ||||
| 	for i := range rows { | ||||
| 		if len(values[i]) != cols { | ||||
| 			return ErrIncompatibleDataDimensions | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	m.rows = rows | ||||
| 	m.cols = cols | ||||
| 	m.values = values | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // MarshalJSON implements json.Marshaler for Matrix, serializing the matrix as | ||||
| // a JSON two-dimensional array. | ||||
| func (m *Matrix[T]) MarshalJSON() ([]byte, error) { | ||||
| 	return json.Marshal(m.values) | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package matrix_test | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"testing" | ||||
|  | ||||
| 	"git.omicron.one/playground/cryptography/matrix" | ||||
| @@ -55,6 +56,41 @@ func TestCreate(t *testing.T) { | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestCreateFromSlice(t *testing.T) { | ||||
| 	m := matrix.CreateFromSlice([][]int{ | ||||
| 		{1, 2, 3}, | ||||
| 		{4, 5, 6}, | ||||
| 	}) | ||||
| 	assert.NotNil(t, m) | ||||
|  | ||||
| 	assert.Equal(t, 2, m.Rows()) | ||||
| 	assert.Equal(t, 3, m.Cols()) | ||||
|  | ||||
| 	assert.Equal(t, 1, m.Get(0, 0)) | ||||
| 	assert.Equal(t, 2, m.Get(0, 1)) | ||||
| 	assert.Equal(t, 3, m.Get(0, 2)) | ||||
| 	assert.Equal(t, 4, m.Get(1, 0)) | ||||
| 	assert.Equal(t, 5, m.Get(1, 1)) | ||||
| 	assert.Equal(t, 6, m.Get(1, 2)) | ||||
|  | ||||
| 	assert.PanicsWithValue(t, matrix.ErrInvalidDimensions, func() { | ||||
| 		matrix.CreateFromSlice([][]int{}) | ||||
| 	}) | ||||
|  | ||||
| 	assert.PanicsWithValue(t, matrix.ErrInvalidDimensions, func() { | ||||
| 		matrix.CreateFromSlice([][]int{ | ||||
| 			{}, | ||||
| 		}) | ||||
| 	}) | ||||
|  | ||||
| 	assert.PanicsWithValue(t, matrix.ErrIncompatibleDataDimensions, func() { | ||||
| 		matrix.CreateFromSlice([][]int{ | ||||
| 			{1, 2, 3}, | ||||
| 			{4, 5}, | ||||
| 		}) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestCreateFromFlatSlice(t *testing.T) { | ||||
| 	m := matrix.CreateFromFlatSlice(2, 3, []int{1, 2, 3, 4, 5, 6}) | ||||
| 	assert.NotNil(t, m) | ||||
| @@ -94,6 +130,46 @@ func TestCreateFromFlatSlice(t *testing.T) { | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestCreateFromJSON(t *testing.T) { | ||||
| 	// data json | ||||
| 	data := []byte(`[[1, 2, 3], [4, 5, 6]]`) | ||||
| 	m, err := matrix.CreateFromJSON[int](data) | ||||
| 	assert.Nil(t, err) | ||||
| 	assert.NotNil(t, m) | ||||
| 	assert.Equal(t, 2, m.Rows()) | ||||
| 	assert.Equal(t, 3, m.Cols()) | ||||
| 	assert.Equal(t, 1, m.Get(0, 0)) | ||||
| 	assert.Equal(t, 2, m.Get(0, 1)) | ||||
| 	assert.Equal(t, 3, m.Get(0, 2)) | ||||
| 	assert.Equal(t, 4, m.Get(1, 0)) | ||||
| 	assert.Equal(t, 5, m.Get(1, 1)) | ||||
| 	assert.Equal(t, 6, m.Get(1, 2)) | ||||
|  | ||||
| 	// invalid json | ||||
| 	data = []byte(`[[1, 2, 3], [4, 5,`) | ||||
| 	m, err = matrix.CreateFromJSON[int](data) | ||||
| 	assert.NotNil(t, err) | ||||
| 	assert.Nil(t, m) | ||||
|  | ||||
| 	// empty matrix | ||||
| 	data = []byte(`[]`) | ||||
| 	m, err = matrix.CreateFromJSON[int](data) | ||||
| 	assert.ErrorIs(t, err, matrix.ErrInvalidDimensions) | ||||
| 	assert.Nil(t, m) | ||||
|  | ||||
| 	// empty rows | ||||
| 	data = []byte(`[[]]`) | ||||
| 	m, err = matrix.CreateFromJSON[int](data) | ||||
| 	assert.ErrorIs(t, err, matrix.ErrInvalidDimensions) | ||||
| 	assert.Nil(t, m) | ||||
|  | ||||
| 	// mixed row length | ||||
| 	data = []byte(`[[1, 2, 3], [4, 5]]`) | ||||
| 	m, err = matrix.CreateFromJSON[int](data) | ||||
| 	assert.ErrorIs(t, err, matrix.ErrIncompatibleDataDimensions) | ||||
| 	assert.Nil(t, m) | ||||
| } | ||||
|  | ||||
| func TestSum(t *testing.T) { | ||||
| 	a := matrix.CreateFromFlatSlice(2, 3, []int{1, 2, 3, 4, 5, 6}) | ||||
| 	b := matrix.CreateFromFlatSlice(2, 3, []int{1, 1, 1, 1, 1, 1}) | ||||
| @@ -493,3 +569,87 @@ func TestMatrix_Fill(t *testing.T) { | ||||
| 	assert.Equal(t, 3, a.Get(1, 1)) | ||||
| 	assert.Equal(t, 3, a.Get(1, 2)) | ||||
| } | ||||
|  | ||||
| func TestMatrix_UnmarshalJSON(t *testing.T) { | ||||
| 	// int matrix | ||||
| 	data := []byte(`[[1,2,3],[4,5,6]]`) | ||||
| 	var m *matrix.Matrix[int] | ||||
| 	err := json.Unmarshal(data, &m) | ||||
| 	assert.Nil(t, err) | ||||
|  | ||||
| 	assert.Equal(t, 2, m.Rows()) | ||||
| 	assert.Equal(t, 3, m.Cols()) | ||||
| 	assert.Equal(t, 1, m.Get(0, 0)) | ||||
| 	assert.Equal(t, 2, m.Get(0, 1)) | ||||
| 	assert.Equal(t, 3, m.Get(0, 2)) | ||||
| 	assert.Equal(t, 4, m.Get(1, 0)) | ||||
| 	assert.Equal(t, 5, m.Get(1, 1)) | ||||
| 	assert.Equal(t, 6, m.Get(1, 2)) | ||||
|  | ||||
| 	// float matrix | ||||
| 	data = []byte(`[[1.5,2.5],[3.5,4.5]]`) | ||||
| 	var mf *matrix.Matrix[float64] | ||||
| 	err = json.Unmarshal(data, &mf) | ||||
| 	assert.Nil(t, err) | ||||
|  | ||||
| 	assert.Equal(t, 2, mf.Rows()) | ||||
| 	assert.Equal(t, 2, mf.Cols()) | ||||
| 	assert.Equal(t, 1.5, mf.Get(0, 0)) | ||||
| 	assert.Equal(t, 2.5, mf.Get(0, 1)) | ||||
| 	assert.Equal(t, 3.5, mf.Get(1, 0)) | ||||
| 	assert.Equal(t, 4.5, mf.Get(1, 1)) | ||||
|  | ||||
| 	// via json.Unmarshal | ||||
| 	matrices := []byte(`[[[1,2],[3,4]],[[5,6,7]]]`) | ||||
| 	var ms []*matrix.Matrix[int] | ||||
| 	err = json.Unmarshal(matrices, &ms) | ||||
| 	assert.Nil(t, err) | ||||
| 	assert.Len(t, ms, 2) | ||||
| 	assert.Equal(t, 2, ms[0].Get(0, 1)) | ||||
| 	assert.Equal(t, 7, ms[1].Get(0, 2)) | ||||
|  | ||||
| 	// invalid JSON | ||||
| 	err = m.UnmarshalJSON([]byte(`invalid`)) | ||||
| 	assert.NotNil(t, err) | ||||
|  | ||||
| 	// empty array | ||||
| 	err = m.UnmarshalJSON([]byte(`[]`)) | ||||
| 	assert.ErrorIs(t, err, matrix.ErrInvalidDimensions) | ||||
|  | ||||
| 	// empty inner array | ||||
| 	err = m.UnmarshalJSON([]byte(`[[]]`)) | ||||
| 	assert.ErrorIs(t, err, matrix.ErrInvalidDimensions) | ||||
|  | ||||
| 	// inconsistent lengths | ||||
| 	err = m.UnmarshalJSON([]byte(`[[1,2],[3]]`)) | ||||
| 	assert.ErrorIs(t, err, matrix.ErrIncompatibleDataDimensions) | ||||
| } | ||||
|  | ||||
| func TestMatrix_MarshallJSON(t *testing.T) { | ||||
| 	// int matrix | ||||
| 	m := matrix.CreateFromSlice([][]int{{1, 2, 3}, {4, 5, 6}}) | ||||
| 	data, err := m.MarshalJSON() | ||||
| 	assert.Nil(t, err) | ||||
| 	assert.NotNil(t, data) | ||||
|  | ||||
| 	expected := `[[1,2,3],[4,5,6]]` | ||||
| 	assert.Equal(t, expected, string(data)) | ||||
|  | ||||
| 	// float matrix | ||||
| 	mf := matrix.CreateFromSlice([][]float64{{1.5, 2.5}, {3.5, 4.5}}) | ||||
| 	data, err = mf.MarshalJSON() | ||||
| 	assert.Nil(t, err) | ||||
| 	assert.NotNil(t, data) | ||||
| 	expectedFloat := `[[1.5,2.5],[3.5,4.5]]` | ||||
| 	assert.Equal(t, expectedFloat, string(data)) | ||||
|  | ||||
| 	// slice of matrices via json.Marshal | ||||
| 	m1 := matrix.CreateFromSlice([][]int{{1, 2}, {3, 4}}) | ||||
| 	m2 := matrix.CreateFromSlice([][]int{{5, 6, 7}}) | ||||
| 	matrices := []*matrix.Matrix[int]{m1, m2} | ||||
|  | ||||
| 	data, err = json.Marshal(matrices) | ||||
| 	assert.Nil(t, err) | ||||
| 	expected = `[[[1,2],[3,4]],[[5,6,7]]]` | ||||
| 	assert.Equal(t, expected, string(data)) | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user