0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

swift で一次元、二次元配列を取り扱うクラスを作る

Last updated at Posted at 2021-03-10

swift で一次元、二次元配列を取り扱うクラスを作る

swiftで一次元、二次元配列を操作するクラスを作成しKMeansで有用性を確認しました
ソースはこちら
配列クラス: https://github.com/ikuo0/swift_number/blob/main/numxd.swift
KMeans実行部: https://github.com/ikuo0/swift_number/blob/main/main.swift

コマンド実行する場合は各々同じディレクトリに設置し

swiftc -emit-executable main.swift numxd.swift

とします、コマンドを実行すると main という名前のファイルが出来ますのでそれを実行してください

KMeans実行部

KMeans部分のソースです、70行ほどでKMeansが実行できていますのでだいぶ簡単に配列操作ができていると思います。

func Test2D() {
    do {
        let n1dI = Num1D<Int>()
        let n1dD = Num1D<Double>()
        let n2d = Num2D<Double>()
        let tsvData = try TSV.Read("seeds_dataset.txt")
        let data = try TSV.ToDouble(tsvData)
        let xIndexes = n1dI.Arange(0, 7) 
        let xData = data.IndexingT(xIndexes)
        let yData = data.LineT(7)
        
        ////////////////////////////////////////
        // scaling
        ////////////////////////////////////////
        let scaleMean = xData.Mean()
        let scaleVriance = xData.Variance()
        let scaleStddev = scaleVriance.Sqrt()
        let scaledX = (xData - scaleMean) / scaleStddev
        
        ////////////////////////////////////////
        // KMeans
        ////////////////////////////////////////
        let Clusters = 3
        let maxIter = 100
        let tol = 1e-5
        
        // Initialize
        let rowIndexes = n1dI.Arange(0, yData.Count)
        let shuffled = rowIndexes.Shuffle()
        let initIndexes = shuffled.Slice(0, Clusters)
        let initMeans = scaledX.Indexing(initIndexes)
        
        var means = initMeans
        let predict = n1dI.Create(xData.Row)
        
        for iter in 0..<maxIter {
            
            // EStep
            for m in 0..<xData.Row {
                let distances = n1dD.Create(Clusters)
                for cluster in 0..<Clusters {
                    let subtract = scaledX.Line(m) - means.Line(cluster)
                    let power = subtract.Power(2)
                    let total = power.Total()
                    let distance = sqrt(total)
                    distances[cluster] = distance
                }
                predict[m] = distances.ArgMin()
            }
            
            // MStep
            let newMeans = n2d.Create(Clusters, scaledX.Col)
            for cluster in 0..<Clusters {
                let cIndexes = predict.WhereEq(cluster)
                let cMean = scaledX.Indexing(cIndexes)
                newMeans[cluster] = cMean.Mean().Value
            }
            
            // threshold
            let subtract = means - newMeans
            let power = subtract.Power(2)
            let total = power.TotalX()
            let meansDistance = sqrt(total / Double(Clusters))
            means = newMeans
            print("iter=\(iter), means distance=\(meansDistance)")
            if meansDistance < tol {
                break
            }
        }
        
        Dump2D(means)
        Dump1D(predict)
        
    } catch {
        print("exception", error)
    }
}

わざわざクラス化する必要があったかどうか

C/C++と異なりメモリ確保の面倒さが無いためわざわざクラス化する事も無かったんじゃないかという気もしましたが、作ってみたら思いの外短く書けているので有りかなと思いました

ジェネリクス関数の書き込みの仕組み

https://github.com/ikuo0/swift_number/blob/main/numxd.swift#L27
自作の WriteGVariable という関数でジェネリクス関数内の代入処理を行っています
WriteGVariable関数を使用しない場合に型を確定させてからキャストをするコードをその都度書かなければいけません

            if T.self is Double.Type {
                dst[i] = Double(r) as! T
            } else if T.self is Int.Type {
                dst[i] = Int(r) as! T
            }
            // 冗長になってしまう

WriteGVariable関数を使用することで1行で代入処理を書くことができます

            WriteGVariable(&dst[i], r)
            // 1行で済む

swiftが厳格な型付け言語であるためにジェネリクス型を使用した場合に冗長になるか冗長回避のため対策が必要になるのは仕方のないことだと思います。
ジェネリクス型に限らず型違い同士の代入処理等については下記URLにて答えが出ているように思います
https://academy.realm.io/jp/posts/richard-fox-casting-swift-2/
https://qiita.com/ObuchiYuki/items/a945efd14d3a05a19f75

答えが出ているにも関わらず真似しないのはoperator演算子のようなソースを隠蔽してしまう仕様が好みではないのと私のswift理解度が低いからです、理解しきれていない物をコピーするのは気が引けますので。

swiftの勉強のためC++で既に実装した物をswiftで実装し直しました、色々と発見があったりしておもしろかったです。
以上です。

0
1
0

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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?