はじめに
別の言語の例を移植すると意外と考えさせられる。
環境
- The Go Playground (2021-12-23)
- Julia 1.6 (Julia Playground)
- Rust 1.5.7 (Rust Playground; rustc)
Rust は手元に環境があるのだが、サンプル程度でプロジェクトを作ったりのが面倒なときは、Playground を利用する。
Julia
計算科学用途な言語だけあって、こういうところは強い。
この宣言の時点で行列の型だと決めつけができる。
A = [1 2 1; 0 1 1]
B = [1 0; 0 1; 1 1]
println(A*B)
[2 3; 1 2]
Go
-
2次元配列
関数も含めてサイズを事前に決めておく必要があり、その結果、任意のサイズの行列の積を行う関数が作れないpackage main import "fmt" func mul(a [2][3]int, b [3][2]int) [2][2]int { var i, j, k int var c [len(a)][len(b[0])]int for i = 0; i < len(a); i++ { for k = 0; k < len(b[0]); k++ { for j = 0; j < len(a[0]); j++ { c[i][k] += a[i][j] * b[j][k] } } } return c } func main() { var a = [2][3]int{ {1, 2, 1}, {0, 1, 1}, } var b = [3][2]int{ {1, 0}, {0, 1}, {1, 1}, } var c = mul(a, b) fmt.Println(c) fmt.Printf("%d %d\n", c[0][0], c[0][1]) fmt.Printf("%d %d\n", c[1][0], c[1][1]) }
実行結果
[[2 3] [1 2]] 2 3 1 2
-
2次元スライス
スライスであればサイズを指定しない宣言ができため、任意のサイズの行列の積を行う関数を実現できる。package main import "fmt" func mul(a [][]int, b [][]int) [][]int { var j, k int c := make([][]int, len(a)) for i := range a { c[i] = make([]int, len(b[0])) for k = 0; k < len(b[0]); k++ { for j = 0; j < len(a[0]); j++ { c[i][k] += a[i][j] * b[j][k] } } } return c } func main() { var a = make([][]int, 2) a[0] = []int{1, 2, 1} a[1] = []int{0, 1, 1} var b = make([][]int, 3) b[0] = []int{1, 0} b[1] = []int{0, 1} b[2] = []int{1, 1} var c = mul(a, b) fmt.Println(c) fmt.Printf("%d %d\n", c[0][0], c[0][1]) fmt.Printf("%d %d\n", c[1][0], c[1][1]) }
実行結果
[[2 3] [1 2]] 2 3 1 2
Rust
C/C++辺りで実装すると必要な malloc/free, new/delete が不要なのは楽。
-
Vec を使った例
Rust の制約として初期化が必要(この辺り、非効率に思えるんだが最適化次第な気がする。)fn mul(a: &Vec<Vec<i32>>, b: &Vec<Vec<i32>>) -> Vec<Vec<i32>> { let mut result = vec![vec![0; a.len()]; b[0].len()]; for i in 0..a.len() { for k in 0..b[0].len() { for j in 0..a[0].len() { result[i][k] += a[i][j] * b[j][k]; } } } result } fn main() { let a = vec![ vec![1, 2, 1], vec![0, 1, 1] ]; let b = vec![ vec![1, 0], vec![0, 1], vec![1, 1] ]; let c = mul(&a, &b); println!("c={:?}", c); }
実行結果
c=[[2, 3], [1, 2]]
-
Generics
0 で初期化する部分は、T::zero()
を実装している必要がある。
Copy がないとa[i][j]
とb[j][k]
で所有権の移動が行われて処理が成立しない。
あとは、*
と+=
のTraitを実装している必要がある。fn mul<T>(a: &Vec<Vec<T>>, b: &Vec<Vec<T>>) -> Vec<Vec<T>> where T: num::traits::Zero, T: Copy, T: std::ops::Mul<Output = T>, T: std::ops::AddAssign<T> { let mut result = vec![vec![T::zero(); a.len()]; b[0].len()]; for i in 0..a.len() { for k in 0..b[0].len() { for j in 0..a[0].len() { result[i][k] += a[i][j] * b[j][k]; } } } result } fn main() { let a = vec![ vec![1, 2, 1], vec![0, 1, 1] ]; let b = vec![ vec![1, 0], vec![0, 1], vec![1, 1] ]; let c = mul::<u32>(&a, &b); println!("c={:?}", c); }
実行結果
c=[[2, 3], [1, 2]]
-
Nalgebra
SMatrix::<u32, 2, 3>
はMatrix2x3
でもよい。extern crate nalgebra as na; fn main() { let a = na::SMatrix::<u32, 2, 3>::new( 1, 2, 1, 0, 1, 1 ); let b = na::SMatrix::<u32, 3, 2>::new( 1, 0, 0, 1, 1, 1 ); let c = a * b; println!("{:?}", c); println!( "{:?} {:?}\n{:?} {:?}", c[(0, 0)], c[(0, 1)], c[(1, 0)], c[(1, 1)] ); }
実行結果
Matrix { data: [[2, 1], [3, 2]] } 2 3 1 2