LoginSignup
11
11

More than 5 years have passed since last update.

【何を血迷ったか】Swift に簡単な「行列」を実装してみた話

Last updated at Posted at 2016-10-06

行列 #とは

大体のことは Wikipedia 先生が教えてくれますが、まあ学校でちゃんと習ってないとなんか言ってることがわからないよね。でもまあぶっちゃけそんなクソ面倒なことやるつもりはないし、ちゃんとした行列や行列式などの演算をやりたいなら swix などのフレームワークもあるので、これはどっちかというと本当に「すべての行は同じ要素数が入ってる」という行列の基本性質を満たしたただの 2 次元配列のデータに過ぎないんじゃないかと思われ。そしてそれを手軽に利用できたらなぁと思っただけです。

目標

  1. 2 次元配列から行列を作れる
  2. 行列の特定要素(数学でいう「(i, j) 番目の要素」)を取得/修正できる
  3. 簡単な行列編集(行の追加/削除、列の追加/削除)ができる
  4. 簡単な行列演算(足し算、引き算、スカラー掛け算、行列掛け算、転置行列)ができる

くらいかな?

実装

まあまず作るも何も、「行列」というものを定義してやんないと話が始まらん。というわけでまず「行列」というやらを定義してあげます。

Matrix.swift
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 次元配列から行列を作る

Matrix.swift
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 という Errorenum を作ります。そして二次元配列が渡されたら、ちゃんと全ての配列が同じ要素数がどうかを確認して、一致したら二次元配列を一次元配列に直して行数と列数をそれぞれ元の二次元配列の中身の配列数とさらにその中の要素数にし、一致しなかったらエラーを吐き出すスロアブルイニシャライザーを作ります。これでいつでも自分の二次元配列を使って Matrix([[element]]) で行列が作れます。

ちなみに最初から同じ要素で指定した行数と列数を行列を作るイニシャライザーも作っておきました

Matrix.swift
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 を組みます:

Matrix.swift
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 も作りました:

Matrix.swift
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 のラベルは明示的に書かないとラベルとして使えないので、行を取り出すか、列を取り出すかを明示的に rowcolumn をラベルとして書いてあげています。これで matrix[column: 0] で 0 列目を取り出せます。

ちなみにここは要素を取り出す時と違って、セッターが定義されておらずゲッターだけしか実装していません。なぜならば Swift の subscript はエラーハンドリングができないので、編集時に行数と列数に厳しい制限のある行列にはそのまま「はいこの行はこの配列で代入してね」っていう行為は非常に危険なのであえて subscript にはそれを定義しませんでした。それをやるのは次の章です

簡単な行列編集(行の追加/削除、列の追加/削除)ができる

先ほど言った通り、行列は編集時に行数と列数に厳しい制限があるので、エラーハンドリングが必要です。というわけでとりあえずまずエラーを定義しておきます

Matrix.swift
public enum MatrixMathError: Error {
    case sizeMismatch
}

次に編集の内容ですが、とりあえず Swift.Array にちなんで、行と列のそれぞれの append(_:)insert(_: at:)remove(_:) 系のメソッドと、自分が Eltaso のフレームワークに Swift.Array の拡張で追加した keep(_:) 系を実装したいと考えています。まずは append(_:)

Matrix.swift
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 の拡張で定義したもので、具体的には以下のように実装しています:

Strideable.swift
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:) ですが、同じように行と列それぞれ定義します

Matrix.swift
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(_:) のメソッドがありますので、それぞれの行と列のバージョンを行列に実装します。全部いっぺんに書くと非常に長いのでまずは比較的に簡単な行から

Matrix.swift
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 では下記のように実装されています:

Int.swift
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 を書き換える感じです。

次にこれの列版を下記のように実装します:

Matrix.swift
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)
    }

}

メソッドは行版とほぼ同じで、RowColumn になっただけです。そして中身のアプローチも概ね行版とそんなに変わってなく、書き換えるのが _rows ではなく _columns になっただけです

そして最後は Eltaso のフレームワークで独自に追加した keep(_:) 系のものですが、これは要するに remove(_:) 系と真逆な操作なので、アプローチとしては全く同じで必要か必要ないかの判定を逆にするだけで済みます

Matrix.swift
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)
    }

}

簡単な行列演算(足し算、引き算、スカラー掛け算、行列掛け算、転置行列)

まあまずは一番簡単な足し算/引き算にしましょうか。これは行列の和演算定義を見ればわかるように非常に単純なもので、行数と列数がそれぞれ一致する二つの行列を同じ場所の要素を足す/引くだけで済みます。ただ行数/列数は必ず一致しないといけないので、さっきの行列編集と同じようにエラーハンドリングが必要ですね。

Matrix.swift
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
}

ここもいきなり独自定義の AdditionOperatableSubtractionOperatable というやらが出てきてしまってますが、これは Eltaso ではこれらに加えて MultiplicationOperatableDivisionOperatable もあり、下記のように定義しています:

ArithmeticOperatable.swift
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 にしか書いてないのです。整数の行列演算にしか手をつけてないのでね…

そして二つの整数行列を足し算したい時、この二つの行列が行数と列数が一致するかを比べ、一致したらそれぞれの要素を足します。まあ簡単ですね、特に言うことはあんまりないのです。

次はスカラー掛け算ですが、これも行列のスカラー倍演算定義を見ると分かりますが非常に簡単です。単純に各要素を指定した数字で掛けるだけです。

Matrix.swift
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
}

さて、問題はこの次の「行列と行列の掛け算」です。これは足し算みたいに、各要素をそのまま掛けてあげればできる演算ではなく、行列の積演算定義を読めばわか…らないかもしれないくらい面倒な演算です。まあこればかりはどうしようもない。詳しく知りたい人は行列の教科書でも漁ってみればいいかもしれません。というわけでとりあえず実装を書いておきます:

Matrix.swift
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 というやらが出てますが、これは先ほどの AdditionOperatableprotocol を少し手を加えて求めたものです:

ArithmeticOperatable.swift
public protocol AdditionOperatable {
    // ...
    static var additionOperationInitialValue: Self { get }
}

extension Int:  AdditionOperatable {
    public static var additionOperationInitialValue: Int {
        return 0
    }
}

これは要するに将来 Int 型以外を AdditionOperatable に対応した時、reduce(_:_:) の初期値を代入できるようにするためのものです。

そして最後に転置行列ですが、これは比較的に簡単なものです、要するに行と列を入れ替えた行列です。

Matrix.swift
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
    }

}

まああとはどうせなら行列の比較もやってみたいですね?というわけで比較も作りました

Matrix.swift
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 リポジトリから確認できます。

あとはご質問とかおツッコミとかどうぞ。

11
11
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
11