行列 #とは
大体のことは Wikipedia 先生が教えてくれますが、まあ学校でちゃんと習ってないとなんか言ってることがわからないよね。でもまあぶっちゃけそんなクソ面倒なことやるつもりはないし、ちゃんとした行列や行列式などの演算をやりたいなら swix などのフレームワークもあるので、これはどっちかというと本当に「すべての行は同じ要素数が入ってる」という行列の基本性質を満たしたただの 2 次元配列のデータに過ぎないんじゃないかと思われ。そしてそれを手軽に利用できたらなぁと思っただけです。
目標
- 2 次元配列から行列を作れる
- 行列の特定要素(数学でいう「(i, j) 番目の要素」)を取得/修正できる
- 簡単な行列編集(行の追加/削除、列の追加/削除)ができる
- 簡単な行列演算(足し算、引き算、スカラー掛け算、行列掛け算、転置行列)ができる
くらいかな?
実装
まあまず作るも何も、「行列」というものを定義してやんないと話が始まらん。というわけでまず「行列」というやらを定義してあげます。
public struct Matrix <Element> {
fileprivate var _value: [Element]
fileprivate var _columns: Int
fileprivate var _rows: Int
}
以上で「行列」というやらの基本定義ができました。これは要するに Array<Element>
と同じように、Matrix<Element>
と言う Element
型の Matrix
だよ、ということ。ちなみにデータの構造は結局ただの Array<Element>
、すなわち [Element]
だけど、最初の実装は実は [[Element]]
の二次元配列にしたのです。なぜこれやめたかというと、二次元配列の場合「0 行 8 列」というような、行が 0 だけど列が複数あるという空行列が表現できないので、やっぱり一次元配列にして行数と列数もそれぞれ変数としてもたせてあげた方がいいのでは?という結論に至りました。
でもこれではまだ全ての変数が fileprivate
になっているので行列を自分で作れないですね。というわけで、次は二次元配列から行列を作るところを実装します
2 次元配列から行列を作る
public enum MatrixInitError: Error {
case rowsWithDifferentColumns
}
public struct Matrix <Element> {
fileprivate var _value: [Element]
fileprivate var _columns: Int
fileprivate var _rows: Int
public init(_ array: [[Element]]) throws {
let rowCount = array.count
let columnCount = array.first?.count ?? 0
for row in array {
guard row.count == columnCount else {
throw MatrixInitError.rowsWithDifferentColumns
}
}
self._value = array.flatMap { $0 }
self._columns = columnCount
self._rows = rowCount
}
}
「Element
」というジェネリック型があるので、どうやら通常の struct
と違って Matrix
の下に enum
が作れない(もしくは作れるかもしれないが別の方法があるかもしれません、教えていただけると大変喜びます)ので仕方なく別のところで MatrixInitError
という Error
の enum
を作ります。そして二次元配列が渡されたら、ちゃんと全ての配列が同じ要素数がどうかを確認して、一致したら二次元配列を一次元配列に直して行数と列数をそれぞれ元の二次元配列の中身の配列数とさらにその中の要素数にし、一致しなかったらエラーを吐き出すスロアブルイニシャライザーを作ります。これでいつでも自分の二次元配列を使って Matrix([[element]])
で行列が作れます。
ちなみに最初から同じ要素で指定した行数と列数を行列を作るイニシャライザーも作っておきました
public struct Matrix <Element> {
// ...
public typealias Size = (m: Int, n: Int)
public var size: Size {
return (self._rows, self._columns)
}
public init(size: Size, repeatedValue: Element) {
let elementCount = size.m * size.n
let elements = (0 ..< elementCount).map { (_) -> Element in
return repeatedValue
}
self._value = elements
self._columns = size.n
self._rows = size.m
}
}
extension Matrix where Element: ExpressibleByIntegerLiteral {
public init(size: Size) {
self.init(size: size, repeatedValue: 0)
}
}
これで Matrix(size: (n: 5, m: 6))
で 5 列 6 行の全ての要素が 0 の行列が作れます
行列の特定要素(数学でいう「(i, j) 番目の要素」)の取得/修正
さてここは強力な subscript
の出番です。通常の二次元配列なら先に行数、その次に列数を指定するのが普通ですが行列に限っては先に縦から数えた行数 i
、次に横から数えた列数 j
を指定するのが普通ですので、下記のように subscript
を組みます:
public struct Matrix <Element> {
// ...
public typealias Index = (i: Int, j: Int)
public subscript(index: Index) -> Element {
get {
let i = index.i, j = index.j
return self[i, j]
}
set {
let i = index.i, j = index.j
self[i, j] = newValue
}
}
public subscript(i: Int, j: Int) -> Element {
get {
let index = i * self._columns + j
return self._value[index]
}
set {
let index = i * self._columns + j
self._value[index] = newValue
}
}
}
これで matrix[Index(i, j)]
もしくは matrix[0, 1]
で行列の (0, 1) 番目の要素を取り出せます。
ちなみに特定要素だけでなく、特定行や特定列を取り出したいこともあると思って、さらに別の subscript
も作りました:
public struct Matrix <Element> {
// ...
public typealias Indices = (columns: CountableRange<Int>, rows: CountableRange<Int>)
public var indices: Indices {
return (0 ..< self._columns, 0 ..< self._rows)
}
public subscript(row i: Int) -> [Element] {
return self.indices.columns.map({ (j) -> Element in
return self[(i, j)]
})
}
public subscript(column j: Int) -> [Element] {
return self.indices.rows.map({ (i) -> Element in
return self[(i, j)]
})
}
}
まずは普通の配列と同じ感覚で、Indices
という範囲型を実装し、それぞれ行範囲と列範囲を定義してあげることで使うときに手軽に範囲が取得できます。
そして subscript
のラベルは明示的に書かないとラベルとして使えないので、行を取り出すか、列を取り出すかを明示的に row
と column
をラベルとして書いてあげています。これで matrix[column: 0]
で 0 列目を取り出せます。
ちなみにここは要素を取り出す時と違って、セッターが定義されておらずゲッターだけしか実装していません。なぜならば Swift の subscript
はエラーハンドリングができないので、編集時に行数と列数に厳しい制限のある行列にはそのまま「はいこの行はこの配列で代入してね」っていう行為は非常に危険なのであえて subscript
にはそれを定義しませんでした。それをやるのは次の章です
簡単な行列編集(行の追加/削除、列の追加/削除)ができる
先ほど言った通り、行列は編集時に行数と列数に厳しい制限があるので、エラーハンドリングが必要です。というわけでとりあえずまずエラーを定義しておきます
public enum MatrixMathError: Error {
case sizeMismatch
}
次に編集の内容ですが、とりあえず Swift.Array
にちなんで、行と列のそれぞれの append(_:)
、insert(_: at:)
、remove(_:)
系のメソッドと、自分が Eltaso のフレームワークに Swift.Array
の拡張で追加した keep(_:)
系を実装したいと考えています。まずは append(_:)
extension Matrix {
public func appendingRow(_ row: [Element]) throws -> Matrix<Element> {
guard row.count == self.size.n else {
throw MatrixMathError.sizeMismatch
}
var matrix = self
matrix._value += row
matrix._rows.increase()
return matrix
}
public mutating func appendRow(_ row: [Element]) throws {
self = try self.appendingRow(row)
}
}
extension Matrix {
public func appendingColumn(_ column: [Element]) throws -> Matrix<Element> {
guard column.count == self.size.m else {
throw MatrixMathError.sizeMismatch
}
var matrix = self
column.enumerated().reversed().forEach { (i, element) in
matrix._value.insert(element, at: i * self._columns + self._columns)
}
matrix._columns.increase()
return matrix
}
public mutating func appendColumn(_ column: [Element]) throws {
self = try self.appendingColumn(column)
}
}
行の追加は非常に簡単で、列のサイズを自分のと比較して合ってるかどうかを確認して、合ってるなら自分の _value
の後ろに追加して、_rows
を一つ増やすだけ。問題は列の方がちょっと面倒。一次元配列にとって列の各要素は配列の中にバラバラにあるし、どこかに要素を追加するとそれ以降の要素の場所が全てずれてしまいます。なのでまずは入れる場所を計算して、入れるときは逆順で後ろから入れてあげないと順番がずれてしまいます。
ちなみに上記のソースコードでは increase()
という命令が使われていますが、これは Eltaso
のフレームワークの Swift.Strideable
の拡張で定義したもので、具体的には以下のように実装しています:
extension Strideable {
public var increased: Self {
return self.advanced(by: 1)
}
public var decreased: Self {
return self.advanced(by: -1)
}
public func increased(by n: Self.Stride) -> Self {
return self.advanced(by: n)
}
public func decreased(by n: Self.Stride) -> Self {
return self.advanced(by: -n)
}
public mutating func increase(by n: Self.Stride = 1) {
self = self.increased(by: n)
}
public mutating func decrease(by n: Self.Stride = 1) {
self = self.decreased(by: n)
}
}
まあ要するに int += 1
を補完効かせたくて int.increase()
で書けるようにしたものです。
これで try matrix.appendColumn([1, 2, 3])
で行列の後ろに [1, 2, 3]
の列を入れることができます
次に insert(_: at:)
ですが、同じように行と列それぞれ定義します
extension Matrix {
public func insertingRow(_ row: [Element], at i: Int) throws -> Matrix<Element> {
guard row.count == self.size.n else {
throw MatrixMathError.sizeMismatch
}
var matrix = self
let initialInsertingIndex = i * self._columns
row.enumerated().forEach { (j, element) in
matrix._value.insert(element, at: initialInsertingIndex + j)
}
matrix._rows.increase()
return matrix
}
public mutating func insertRow(_ row: [Element], at j: Int) throws {
self = try self.insertingRow(row, at: j)
}
}
extension Matrix {
public func insertingColumn(_ column: [Element], at j: Int) throws -> Matrix<Element> {
guard column.count == self.size.m else {
throw MatrixMathError.sizeMismatch
}
var matrix = self
column.enumerated().reversed().forEach { (i, element) in
matrix._value.insert(element, at: i * self._columns + j)
}
matrix._columns.increase()
return matrix
}
public mutating func insertColumn(_ column: [Element], at i: Int) throws {
self = try self.insertingColumn(column, at: i)
}
}
これもさっきの append(_:)
と同じように、行の追加なら決まった場所に要素をぶち込んでやれば OK ですが、列の場合は逆順で入れてあげないと順番が支離破滅しますので逆順で入れてあげねばなりません。そして入れたら行か列のカウンターを上げてあげなければなりません。
さて次は remove(_:)
系ですが、これは Array
ではさらに remove(at:)
、removeFirst(_:)
と removeLast(_:)
のメソッドがありますので、それぞれの行と列のバージョンを行列に実装します。全部いっぺんに書くと非常に長いのでまずは比較的に簡単な行から
extension Matrix {
public func removingRow(at m: Int) -> Matrix<Element> {
let i = m.limited(within: self.indices.rows)
var matrix = self
let rows = Array(self.indices.rows).filter { (row) -> Bool in
return row != i
}.map { (i) -> [Element] in
return self.indices.columns.map({ (j) -> Element in
return matrix[i, j]
})
}
matrix._value = rows.flatten
matrix._rows.decrease()
return matrix
}
public func removingFirstRows(of m: Int) -> Matrix<Element> {
let m = m.limited(within: 0 ... self.size.m)
var matrix = self
let rows = (self._rows - m ..< self._rows).map { (i) -> [Element] in
return self.indices.columns.map({ (j) -> Element in
return matrix[i, j]
})
}
matrix._value = rows.flatten
matrix._rows.decrease(by: m)
return matrix
}
public func removingLastRows(of m: Int) -> Matrix<Element> {
let m = m.limited(within: 0 ... self.size.m)
var matrix = self
let rows = (0 ..< m).map { (i) -> [Element] in
return self.indices.columns.map({ (j) -> Element in
return matrix[i, j]
})
}
matrix._value = rows.flatten
matrix._rows.decrease(by: m)
return matrix
}
public mutating func removeRow(at m: Int) {
self = self.removingRow(at: m)
}
public mutating func removeFirstRows(of m: Int) {
self = self.removingFirstRows(of: m)
}
public mutating func removeLastRows(of m: Int) {
self = self.removingLastRows(of: m)
}
}
ここではまたさらにいきなり独自実装の limited(within:)
のメソッドが出てきてますが、これも Eltaso では下記のように実装されています:
extension Int {
public func limited(within range: Range<Int>) -> Int {
switch self {
case Int.min ... range.lowerBound:
return range.lowerBound
case range.upperBound.decreased ... Int.max:
return range.upperBound.decreased
default:
return self
}
}
public func limited(within range: ClosedRange<Int>) -> Int {
switch self {
case Int.min ... range.lowerBound:
return range.lowerBound
case range.upperBound ... Int.max:
return range.upperBound
default:
return self
}
}
public func limited(within range: CountableRange<Int>) -> Int {
let range = Range(range)
return self.limited(within: range)
}
public func limited(within range: CountableClosedRange<Int>) -> Int {
let range = ClosedRange(range)
return self.limited(within: range)
}
public mutating func limit(within range: Range<Int>) {
self = self.limited(within: range)
}
public mutating func limit(within range: ClosedRange<Int>) {
self = self.limited(within: range)
}
public mutating func limit(within range: CountableRange<Int>) {
self = self.limited(within: range)
}
public mutating func limit(within range: CountableClosedRange<Int>) {
self = self.limited(within: range)
}
}
まあこれは要するに let a = b <= 0 ? 0 : b >= 10 ? 9 : b
という書くのも面倒だし改行しないと読むのもクソだるいものを a = b.limited(within: 0 ..< 10)
に書き直せるメソッドです。このメソッドで何をしたいかというと、渡された行数がきちんとこの行列の中の行に入っている、仮にもし入っていなかったらそれに最も近い行に直すということです。
そして removeRow(at:)
は行列の指定した行を削除し、removeFirstRows(of:)
は指定した先頭 m 行を削除し、removeLastRows(of:)
は指定した末尾 m 行を削除するメソッドです。基本的なアプローチは行と列をそれぞれ走査して、必要なものだけ残して _value
と _rows
を書き換える感じです。
次にこれの列版を下記のように実装します:
extension Matrix {
public func removingColumn(at n: Int) -> Matrix<Element> {
let j = n.limited(within: self.indices.columns)
var matrix = self
let rows = self.indices.rows.map { (i) -> [Element] in
return Array(self.indices.columns).filter({ (column) -> Bool in
return column != j
}).map({ (i) -> Element in
return matrix[i, j]
})
}
matrix._value = rows.flatten
matrix._columns.decrease()
return matrix
}
public func removingFirstColumns(of n: Int) -> Matrix<Element> {
let n = n.limited(within: 0 ... self.size.n)
var matrix = self
let rows = self.indices.rows.map { (i) -> [Element] in
return (n ..< self._columns).map({ (j) -> Element in
return matrix[i, j]
})
}
matrix._value = rows.flatten
matrix._columns.decrease(by: n)
return matrix
}
public func removingLastColumns(of n: Int) -> Matrix<Element> {
let n = n.limited(within: 0 ... self.size.n)
var matrix = self
let rows = self.indices.rows.map { (i) -> [Element] in
return (0 ..< self._columns - n).map({ (j) -> Element in
return matrix[i, j]
})
}
matrix._value = rows.flatten
matrix._columns.decrease(by: n)
return matrix
}
public mutating func removeColumn(at n: Int) {
self = self.removingColumn(at: n)
}
public mutating func removeFirstColumns(of n: Int) {
self = self.removingFirstColumns(of: n)
}
public mutating func removeLastColumns(of n: Int) {
self = self.removingLastColumns(of: n)
}
}
メソッドは行版とほぼ同じで、Row
が Column
になっただけです。そして中身のアプローチも概ね行版とそんなに変わってなく、書き換えるのが _rows
ではなく _columns
になっただけです
そして最後は Eltaso のフレームワークで独自に追加した keep(_:)
系のものですが、これは要するに remove(_:)
系と真逆な操作なので、アプローチとしては全く同じで必要か必要ないかの判定を逆にするだけで済みます
extension Matrix {
public func keepingRow(at m: Int) -> Matrix<Element> {
let i = m.limited(within: self.indices.rows)
var matrix = self
let row = matrix.indices.columns.map({ (j) -> Element in
return matrix[i, j]
})
matrix._value = row
matrix._rows = 1
return matrix
}
public func keepingFirstRows(of m: Int) -> Matrix<Element> {
let m = m.limited(within: 0 ... self.size.m)
var matrix = self
let rows = (0 ..< m).map { (i) -> [Element] in
return self.indices.columns.map({ (j) -> Element in
return matrix[i, j]
})
}
matrix._value = rows.flatten
matrix._rows = m
return matrix
}
public func keepingLastRows(of m: Int) -> Matrix<Element> {
let m = m.limited(within: 0 ... self.size.m)
var matrix = self
let rows = (self._rows - m ..< self._rows).map { (i) -> [Element] in
return self.indices.columns.map({ (j) -> Element in
return matrix[i, j]
})
}
matrix._value = rows.flatten
matrix._rows = m
return matrix
}
public mutating func keepRow(at m: Int) {
self = self.keepingRow(at: m)
}
public mutating func keepFirstRows(of m: Int) {
self = self.keepingFirstRows(of: m)
}
public mutating func keepLastRows(of m: Int) {
self = self.keepingLastRows(of: m)
}
}
extension Matrix {
public func keepingColumn(at n: Int) -> Matrix<Element> {
let j = n.limited(within: self.indices.columns)
var matrix = self
let column = self.indices.rows.map { (i) -> Element in
return matrix[i, j]
}
matrix._value = column
matrix._columns = 1
return matrix
}
public func keepingFirstColumns(of n: Int) -> Matrix<Element> {
let n = n.limited(within: 0 ... self.size.n)
var matrix = self
let rows = self.indices.rows.map { (i) -> [Element] in
return (0 ..< n).map({ (j) -> Element in
return matrix[i, j]
})
}
matrix._value = rows.flatten
matrix._columns = n
return matrix
}
public func keepingLastColumns(of n: Int) -> Matrix<Element> {
let n = n.limited(within: 0 ... self.size.n)
var matrix = self
let rows = self.indices.rows.map { (i) -> [Element] in
return (self._columns - n ..< self._columns).map({ (j) -> Element in
return matrix[i, j]
})
}
matrix._value = rows.flatten
matrix._columns = n
return matrix
}
public mutating func keepColumn(at n: Int) {
self = self.keepingColumn(at: n)
}
public mutating func keepFirstColumns(of n: Int) {
self = self.keepingFirstColumns(of: n)
}
public mutating func keepLastColumns(of n: Int) {
self = self.keepingLastColumns(of: n)
}
}
簡単な行列演算(足し算、引き算、スカラー掛け算、行列掛け算、転置行列)
まあまずは一番簡単な足し算/引き算にしましょうか。これは行列の和演算定義を見ればわかるように非常に単純なもので、行数と列数がそれぞれ一致する二つの行列を同じ場所の要素を足す/引くだけで済みます。ただ行数/列数は必ず一致しないといけないので、さっきの行列編集と同じようにエラーハンドリングが必要ですね。
public func + <T> (lhs: Matrix<T>, rhs: Matrix<T>) throws -> Matrix<T> where T: AdditionOperatable {
guard lhs.size == rhs.size else {
throw MatrixMathError.sizeMismatch
}
var matrix = lhs
for i in matrix.indices.rows {
for j in matrix.indices.columns {
matrix[i, j] += rhs[i, j]
}
}
return matrix
}
public func += <T> (lhs: inout Matrix<T>, rhs: Matrix<T>) throws where T: AdditionOperatable {
lhs = try lhs + rhs
}
public func - <T> (lhs: Matrix<T>, rhs: Matrix<T>) throws -> Matrix<T> where T: SubtractionOperatable {
guard lhs.size == rhs.size else {
throw MatrixMathError.sizeMismatch
}
var matrix = lhs
for i in matrix.indices.rows {
for j in matrix.indices.columns {
matrix[i, j] -= rhs[i, j]
}
}
return matrix
}
public func -= <T> (lhs: inout Matrix<T>, rhs: Matrix<T>) throws where T: SubtractionOperatable {
lhs = try lhs - rhs
}
ここもいきなり独自定義の AdditionOperatable
と SubtractionOperatable
というやらが出てきてしまってますが、これは Eltaso ではこれらに加えて MultiplicationOperatable
と DivisionOperatable
もあり、下記のように定義しています:
public protocol AdditionOperatable {
static func + (lhs: Self, rhs: Self) -> Self
static func += ( lhs: inout Self, rhs: Self)
}
public protocol SubtractionOperatable {
static func - (lhs: Self, rhs: Self) -> Self
static func -= ( lhs: inout Self, rhs: Self)
}
public protocol MultiplicationOperatable {
static func * (lhs: Self, rhs: Self) -> Self
static func *= ( lhs: inout Self, rhs: Self)
}
public protocol DivisionOperatable {
static func / (lhs: Self, rhs: Self) -> Self
static func /= ( lhs: inout Self, rhs: Self)
}
extension Int: AdditionOperatable {}
extension Int: SubtractionOperatable {}
extension Int: MultiplicationOperatable {}
extension Int: DivisionOperatable {}
Int
型は元から足し算/引き算に対応しているので、単に「こう言う protocol
を定義したよ、行列演算の時は要素がこの protocol
に対応していないと行列演算できないよ」という制限を入れただけです。
ちなみに今は Int
にしか書いてないのです。整数の行列演算にしか手をつけてないのでね…
そして二つの整数行列を足し算したい時、この二つの行列が行数と列数が一致するかを比べ、一致したらそれぞれの要素を足します。まあ簡単ですね、特に言うことはあんまりないのです。
次はスカラー掛け算ですが、これも行列のスカラー倍演算定義を見ると分かりますが非常に簡単です。単純に各要素を指定した数字で掛けるだけです。
public func * <T> (lhs: T, rhs: Matrix<T>) -> Matrix<T> where T: MultiplicationOperatable {
var matrix = rhs
for j in matrix.indices.rows {
for i in matrix.indices.columns {
matrix[i, j] *= lhs
}
}
return matrix
}
public func * <T> (lhs: Matrix<T>, rhs: T) -> Matrix<T> where T: MultiplicationOperatable {
return rhs * lhs
}
public func *= <T> (lhs: inout Matrix<T>, rhs: T) where T: MultiplicationOperatable {
lhs = lhs * rhs
}
さて、問題はこの次の「行列と行列の掛け算」です。これは足し算みたいに、各要素をそのまま掛けてあげればできる演算ではなく、行列の積演算定義を読めばわか…らないかもしれないくらい面倒な演算です。まあこればかりはどうしようもない。詳しく知りたい人は行列の教科書でも漁ってみればいいかもしれません。というわけでとりあえず実装を書いておきます:
public func * <T> (lhs: Matrix<T>, rhs: Matrix<T>) throws -> Matrix<T> where T: AdditionOperatable, T: MultiplicationOperatable {
guard lhs.size.n == rhs.size.m else {
throw MatrixMathError.sizeMismatch
}
let resultSize: Matrix<T>.Size = (lhs.size.m, rhs.size.n)
let resultIndices: Matrix<T>.Indices = (lhs.indices.rows, rhs.indices.columns)
let resultElementFactorIndices: CountableRange<Int> = lhs.indices.columns
func getMultipliedValue(at index: Matrix<T>.Index) -> T {
return resultElementFactorIndices.reduce(T.additionOperationInitialValue) { (result, indice) -> T in
let factor = lhs[index.i, indice] * rhs[indice, index.j]
return result + factor
}
}
let value = resultIndices.rows.map { (i) -> [T] in
return resultIndices.columns.map({ (j) -> T in
return getMultipliedValue(at: (i, j))
})
}
return try Matrix(value)
}
public func *= <T> (lhs: inout Matrix<T>, rhs: Matrix<T>) throws where T: AdditionOperatable, T: MultiplicationOperatable {
lhs = try lhs * rhs
}
まあなかなか面倒な計算なので細かい説明は省けますが、足し算とかと同じように各要素を行と列をそれぞれ走査して、getMultipliedValue(at:)
の関数で結果を求めます。
ちなみにここで T.additionOperationInitialValue
というやらが出てますが、これは先ほどの AdditionOperatable
の protocol
を少し手を加えて求めたものです:
public protocol AdditionOperatable {
// ...
static var additionOperationInitialValue: Self { get }
}
extension Int: AdditionOperatable {
public static var additionOperationInitialValue: Int {
return 0
}
}
これは要するに将来 Int
型以外を AdditionOperatable
に対応した時、reduce(_:_:)
の初期値を代入できるようにするためのものです。
そして最後に転置行列ですが、これは比較的に簡単なものです、要するに行と列を入れ替えた行列です。
extension Matrix {
public var transposed: Matrix {
var transposed = self
transposed._columns = self._rows
transposed._rows = self._columns
for i in transposed.indices.rows {
for j in transposed.indices.columns {
transposed[i, j] = self[j, i]
}
}
return transposed
}
public mutating func transpose() {
self = self.transposed
}
}
まああとはどうせなら行列の比較もやってみたいですね?というわけで比較も作りました
public func == <T> (lhs: Matrix<T>, rhs: Matrix<T>) -> Bool where T: Equatable {
guard lhs.size == rhs.size else {
return false
}
for i in 0 ..< lhs.size.m {
for j in 0 ..< lhs.size.n {
if lhs[i, j] != rhs[i, j] {
return false
}
}
}
return true
}
public func != <T> (lhs: Matrix<T>, rhs: Matrix<T>) -> Bool where T: Equatable {
return !(lhs == rhs)
}
あとがき
まあここまで作ったら簡単な行列演算には使えるし、それ以外でも「同じ要素数を持つ各配列から作られた二次元配列」というものにも使えます。もっと手を加えたいのならクソ面倒な行列式とかの対応もできなくはないがそこまでやらなくてもいいじゃないの?と思ったり。最初に言った通りそこまでガチな行列演算をやりたいのなら多分専門的なライブラリーを導入した方がよろしいかと。これはあくまで Eltaso という俺得シュガーシンタックスフレームワークで実現した手軽に使える簡単な行列型ですのでw
そしてフルのソースコードは Github の Eltaso リポジトリから確認できます。
あとはご質問とかおツッコミとかどうぞ。