Swiftで行列を鑑みた多次元配列を宣言、生成する

プログラムに配列は欠かせず、 iPhoneアプリのゲーム開発に於いても其れは変わらず、 併せてゲーム内のデータ保持に行列を用いたいとなれば必須事項ですが、 型に厳格な言語 Swift 上の話でもあれば、 実行しようと思うもなかなかに難儀で、 エラーを頻発させるも諦める訳にも行かず、 何とか形になった経緯を本記事に記しおくものです。

アプリあちこち食堂(仮題)に於ける多次元配列を可視化した開発画面
アプリあちこち食堂(仮題)に於ける多次元配列を可視化した開発画面

求める2次元配列の形

自らの策定した仕様の要請に依れば手始めに 以下の如き2次元の行列を鑑みた Bool型の2次元配列を生成したくあります。

10000000000001
10000000000001
10000000000001
10000000000001
10000000000001
10000000000001
10000000000001
10000000000001
10000000000001
10000000000001
11111111111111

処が先ず以て、 Xcode は此の宣言さえ許してくれません。 下手に宣言すればビルドは通るものの、 走らせると配列forループでお馴染みのエラー Fatal error: Index out of range が吐き出されて止まってしまいます。

どうもネットを繰るとteratailに以下の様な遣り取りが見付かり…

スポンサーリンク
日付:2018年5月12日、13日
開発機:MacBook Pro (13-inch, Late 2016, Four Thunderbolt 3 Ports)
MacOSバージョン:macOS High Sierra 10.13.4
Xcodeバージョン:9.3
言語:Swift 4.1
主関連アプリ:あちこち食堂(仮題)

誰しもが多次元配列の扱いには苦労しているのだ、 と少し安心したりもします。 どうやらSwiftの2次元行列は、行数、列数の設定に厳しいらしく読み取れます。 Swiftに限らず、多次元配列の宣言、初期化に於いては、 要素を確り把握して其れ等を余すところなく盛り込まねばならない様です。

Swift - 【Swift】多次元配列の2つ目の要素に追加(append)できない(83168)

2次元配列の宣言

加えてSwiftの多次元配列の宣言に絞ってネットを繰って見ると、 以下の様な情報が見付かります。

  • [swift]多次元配列の初期化(ntの備忘録:)
    • 正:var stringArray = [[String]]()
    • 誤:var stringArray:[[String]] = [[]]
    • ビルドが通ったので、一見よさげに思いましたが、実際に処理させてみると、空の配列が先頭にできてしまい…
  • Swiftで多次元配列を使う場合(Qiita:)
    • 正:var test = [[string]]()
    • 誤:var test:[[string]] = [[]]
    • 誤った宣言の場合はbuildは通るが、初期化の時点で配列の先頭に空の配列が出来てしまうのでNG。
  • 上記情報を参照して求める2次元配列を、 クラス変数として以下の様な宣言をし、 加えてforループで要素の追加を実行しました。

    public var requiredAry:[[Bool]] = [[Bool]]()

    for i in 0..<vCnt {
      for j in 0..<hCnt {
        if i == vCnt - 1 {
          requiredkAry[i].append(true)
        } else {
          if j == 1 {
            requiredkAry[i].append(true)
          } else if j == vCnt - 1 {
            requiredkAry[i].append(true)
          } else {
            requiredkAry[i].append(false)
          }
        }
      }
    }

    すると試行錯誤の段階で赤字示した部分の何れかに於いて例の Thread 1: Fatal error: Index out of range エラーが吐き出されました。 其処で2次元配列の初期化に絞ってネットを繰ると以下の 知識は力なり、知識は宝なり。 ブログの2015年2月11日の記事が見付かりました。

    Swift 2次元配列初期化

    Bool型2次元配列の初期化

    上記、記事内容を参照の上、書いたのが以下の初期化コードですが、 どうやら上手く機能してくれた様です。

    requiredAry = [[Bool]](repeating: [Bool](repeating: false, count: vCnt), count: hCnt)

    上記コードは全ての要素を偽としていますので、 冒頭で望む形に要素を入れ替えます。 行数、列数に加え上下方向も上手く混乱する様に込み入っていていますが、 首尾良く機能したコードと其の結果をコンソール出力したものが以下です。

    for i in 0..<vCnt {
      for j in 0..<hCnt {
        if i == vCnt - 1 {
          requiredAry[i][j] = true
        } else {
          if j == 0 {
            requiredAry[i][j] = true
          } else if j == vCnt - 1 {
            requiredAry[i][j] = true
          }
        }
      }
    }
    [
      [true, false, false, false, false, false, false, false, false, false, false, true],
      [true, false, false, false, false, false, false, false, false, false, false, true],
      [true, false, false, false, false, false, false, false, false, false, false, true],
      [true, false, false, false, false, false, false, false, false, false, false, true],
      [true, false, false, false, false, false, false, false, false, false, false, true],
      [true, false, false, false, false, false, false, false, false, false, false, true],
      [true, false, false, false, false, false, false, false, false, false, false, true],
      [true, false, false, false, false, false, false, false, false, false, false, true],
      [true, false, false, false, false, false, false, false, false, false, false, true],
      [true, false, false, false, false, false, false, false, false, false, false, true],
      [true, false, false, false, false, false, false, false, false, false, false, true],
      [true, true, true, true, true, true, true, true, true, true, true, true]
    ]

    CGFloat型3次元配列

    求めるBool型2次元配列の生成は首尾よく運びましたので、 次にCGFloat型3次元配列を生成したくありす。 此の3次元配列の要請としては上で求めたBool型2次元配列の真偽要素が其々 X座標、Y座標の2要素を保持する配列にしたいもので、 従ってCGFloat型3次元配列となります。

    今回、此処で要らぬ時間を喰ったのは書式に於いてでした。 以下の様に書くと Type of expression is ambiguous without more context なるエラーをXcodeに吐き出されてしまいます。

    coordinateAry = [[[CGFloat]]](repeating: [[CGFloat]](repeating: [CGFloat](repeating: CGFloat, count: 2), count: hCnt), count: vCnt)

    曖昧だとの指摘ですが皆目見当が付かず余計な時間が掛かってしまいましたが、 Xcodeの補完を利用しながら一から書いていくと、 どうやら最後にネストされる [CGFloat](repeating: CGFloat, count: 2) のCGFloat部分で此れをCGFloat()と括弧付きにしないのが問題であったようです。 従ってエラーを招かない記述は以下の如くなります。

    coordinateAry = [[[CGFloat]]](repeating: [[CGFloat]](repeating: [CGFloat](repeating: CGFloat(), count: 2), count: hCnt), count: vCnt)

    別初期化方法と今回手法決定の理由

    此のエラーさえ排除出来れば後は2次元配列の際と同様ですが、 初期化の方法としては別に此のような方法を取らなくとも以下の様に取り敢えずは力尽くで初期化して、 生成された配列に.append()したり、更新したりして望む処の形に出来るようではあります。

    coordinateAry = [
      [
        [0,0], [0, 0], [0, 0]
      ],
      [
        [0,0], [0, 0], [0, 0]
      ]
    ]

    ただ個人的に好みの格好ではなく、 従って全体像の把握もし辛く、 要らぬ空要素が先頭などに混入したりしてバグの温床にもなり兼ねなくなる感もあり、 畢竟、一度、要素を決定した配列を生成してから要素を入れ替える方法の方が、 個人的には効率的、効果的にコード記述を進められる感じがあって。 本記事に記す処の手法にて要請を満たそうとしたものです。

    最終的な実コードと出力

    GFloat型3次元配列の初期化は前記しましたが、其の前の宣言はクラス変数として以下の如くしました。

    public var fixUnitCoordinateAry:[[[CGFloat]]] = [[[CGFloat]]]()

    冒頭の アプリあちこち食堂(仮題)に於ける多次元配列を可視化した開発画面 ではデバッグ用のゲーム画面としてグリッドを敷き詰めて描画したものですが、 其れに合わせて正式に利用するX座標、Y座標の行列を要素に持つ配列 coordinateAry 、即ちGFloat型3次元配列を同時に成型しているのが下のコードです。

    var i:Int = objClass.vCnt - 1
    let gridSize:CGFloat = objClass.gridSize
    for ary_i in objClass.requiredAry {
      var j = 0
      for elem_j in ary_i {
        let rectSize:CGSize = CGSize(width: 5, height: 5)
        var rectCol:UIColor = UIColor.green
        var rectPos:CGPoint!
        let offset:CGFloat = gridSize / 2
        let offsetY:CGFloat = objClass.position.y + offset
        if elem_j {
          rectCol = UIColor.red
          rectPos = CGPoint(x: gridSize * CGFloat(j) + offset, y: gridSize * CGFloat(i) + offsetY)
        } else {
          rectCol = UIColor.blue
          rectPos = CGPoint(x: gridSize * CGFloat(j) + offset, y: gridSize * CGFloat(i) + offsetY)
        }
        let rect = SKShapeNode(rectOf: rectSize)
        rect.position = rectPos
        rect.strokeColor = rectCol
        self.addChild(rect)
        coordinateAry[i][j][0] = gridSize * CGFloat(j) + offset
        coordinateAry[i][j][1] = gridSize * CGFloat(i) + offsetY
        j += 1
      }
      i -= 1
    }
    [
      [
        [18.8181818181818, 212.880681818182],
        [56.4545454545455, 212.880681818182],
        [94.0909090909091, 212.880681818182],
        [131.727272727273, 212.880681818182],
        [169.363636363636, 212.880681818182],
        [207.0, 212.880681818182],
        [244.636363636364, 212.880681818182],
        [282.272727272727, 212.880681818182],
        [319.909090909091, 212.880681818182],
        [357.545454545455, 212.880681818182],
        [395.181818181818, 212.880681818182]
      ],
      [
        [18.8181818181818, 250.517045454545],
        [56.4545454545455, 250.517045454545],
        [94.0909090909091, 250.517045454545],
        [131.727272727273, 250.517045454545],
        [169.363636363636, 250.517045454545],
        [207.0, 250.517045454545],
        [244.636363636364, 250.517045454545],
        [282.272727272727, 250.517045454545],
        [319.909090909091, 250.517045454545],
        [357.545454545455, 250.517045454545],
        [395.181818181818, 250.517045454545]
      ],
      [
        [18.8181818181818, 288.153409090909],
        [56.4545454545455, 288.153409090909],
        [94.0909090909091, 288.153409090909],
        [131.727272727273, 288.153409090909],
        [169.363636363636, 288.153409090909],
        [207.0, 288.153409090909],
        [244.636363636364, 288.153409090909],
        [282.272727272727, 288.153409090909],
        [319.909090909091, 288.153409090909],
        [357.545454545455, 288.153409090909],
        [395.181818181818, 288.153409090909]
      ],
      [
        [18.8181818181818, 325.789772727273],
        [56.4545454545455, 325.789772727273],
        [94.0909090909091, 325.789772727273],
        [131.727272727273, 325.789772727273],
        [169.363636363636, 325.789772727273],
        [207.0, 325.789772727273],
        [244.636363636364, 325.789772727273],
        [282.272727272727, 325.789772727273],
        [319.909090909091, 325.789772727273],
        [357.545454545455, 325.789772727273],
        [395.181818181818, 325.789772727273]
      ],
      [
        [18.8181818181818, 363.426136363636],
        [56.4545454545455, 363.426136363636],
        [94.0909090909091, 363.426136363636],
        [131.727272727273, 363.426136363636],
        [169.363636363636, 363.426136363636],
        [207.0, 363.426136363636],
        [244.636363636364, 363.426136363636],
        [282.272727272727, 363.426136363636],
        [319.909090909091, 363.426136363636],
        [357.545454545455, 363.426136363636],
        [395.181818181818, 363.426136363636]
      ],
      [
        [18.8181818181818, 401.0625],
        [56.4545454545455, 401.0625],
        [94.0909090909091, 401.0625],
        [131.727272727273, 401.0625],
        [169.363636363636, 401.0625],
        [207.0, 401.0625],
        [244.636363636364, 401.0625],
        [282.272727272727, 401.0625],
        [319.909090909091, 401.0625],
        [357.545454545455, 401.0625],
        [395.181818181818, 401.0625]
      ],
      [
        [18.8181818181818, 438.698863636364],
        [56.4545454545455, 438.698863636364],
        [94.0909090909091, 438.698863636364],
        [131.727272727273, 438.698863636364],
        [169.363636363636, 438.698863636364],
        [207.0, 438.698863636364],
        [244.636363636364, 438.698863636364],
        [282.272727272727, 438.698863636364],
        [319.909090909091, 438.698863636364],
        [357.545454545455, 438.698863636364],
        [395.181818181818, 438.698863636364]
      ],
      [
        [18.8181818181818, 476.335227272727],
        [56.4545454545455, 476.335227272727],
        [94.0909090909091, 476.335227272727],
        [131.727272727273, 476.335227272727],
        [169.363636363636, 476.335227272727],
        [207.0, 476.335227272727],
        [244.636363636364, 476.335227272727],
        [282.272727272727, 476.335227272727],
        [319.909090909091, 476.335227272727],
        [357.545454545455, 476.335227272727],
        [395.181818181818, 476.335227272727]
      ],
      [
        [18.8181818181818, 513.971590909091],
        [56.4545454545455, 513.971590909091],
        [94.0909090909091, 513.971590909091],
        [131.727272727273, 513.971590909091],
        [169.363636363636, 513.971590909091],
        [207.0, 513.971590909091],
        [244.636363636364, 513.971590909091],
        [282.272727272727, 513.971590909091],
        [319.909090909091, 513.971590909091],
        [357.545454545455, 513.971590909091],
        [395.181818181818, 513.971590909091]
      ],
      [
        [18.8181818181818, 551.607954545455],
        [56.4545454545455, 551.607954545455],
        [94.0909090909091, 551.607954545455],
        [131.727272727273, 551.607954545455],
        [169.363636363636, 551.607954545455],
        [207.0, 551.607954545455],
        [244.636363636364, 551.607954545455],
        [282.272727272727, 551.607954545455],
        [319.909090909091, 551.607954545455],
        [357.545454545455, 551.607954545455],
        [395.181818181818, 551.607954545455]
      ],
      [
        [18.8181818181818, 589.244318181818],
        [56.4545454545455, 589.244318181818],
        [94.0909090909091, 589.244318181818],
        [131.727272727273, 589.244318181818],
        [169.363636363636, 589.244318181818],
        [207.0, 589.244318181818],
        [244.636363636364, 589.244318181818],
        [282.272727272727, 589.244318181818],
        [319.909090909091, 589.244318181818],
        [357.545454545455, 589.244318181818],
        [395.181818181818, 589.244318181818]
      ]
    ]

    些か冗長ですが、得られた配列も配列の成型コードの下に続けて、 此の上に記しおきました。

    此の配列の各座標は本記事冒頭画像の アプリあちこち食堂(仮題)に於ける多次元配列を可視化した開発画面 の各グリッドの中心座標を示すものでもあります。 可視化されたゲーム画面を見れば、 どうにか目的を達する為の配列は入手出来た様です。

    結言

    以上から多次元配列生成手順として以下の如くまとめられるでしょう。 然もなくば操作部分に於いて Index out of range エラーの洗礼を受けるのは請け合いです。。

    • 多次元配列宣言
    • 多次元配列初期化
    • 多次元配列操作(追加、更新)
スポンサーリンク