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

多次元配列を1重のループで扱う【JavaScript】

Posted at

このあいだ4次元美術館プレーヤーというのを書きました。美術館というのはパズルの名前で、もとは2次元上の盤面にやたら指向性の高い明かりを置いて、すべてのマスを照らすパズルです。こんな感じ。

akari.png

で、何を思ったかこれを4次元に拡張したパズルを制作する人がいて、2次元でもデジタルの助けが欲しいパズルなのに4次元ともなるとさすがに紙とペンで解くには限界があり、それならということでプレーヤーを作りました。こんな感じです。なんかヤバそうですね。

2020-11-08 12.35.33 4次元美術館プレーヤー.png

って、そんな話はどうでもいいのでした。とにかく、4次元の盤面を生成したり操作したりするのには4次元配列が必要で、すなわち4重ループを回す必要に迫られたのです。

問題:深すぎるネスト

愚直に書くとまーこんな感じです。

for(let x = 0; x < boardX; x++){
    for(let y = 0;y < boardY; y++){
        for(let z = 0; z < boardZ; z++){
            for(let w = 0; w < boardW; w++){
                board[x][y][z][w] = hogehoge()
            }
        }
    }
}

いやこれはさすがにダサい。筆者は別にfor文廃止論者ではありませんが、こんなん誰だって間違うしネストは深過ぎで見づらいし、いいことはなにもない。というわけで、解決策を考えましょう。

解決策①: 余りを用いる

なるべくネストは1重にしたいので、まずはそういうふうに書いてみます。

for(let index=0; index < boardX * boardY * boardZ * boardW; index++){
    let x,y,z,w // どうやって求める?
}

indexからx,y,z,wを求めるには、割り算の余りを使えそうです。

function calcQandR(n,m){
    return [Math.floor(n/m), n%m]
}

function index2pos(i){
    var [i,w] = calcQandR(i,boardW)
    var [i,z] = calcQandR(i,boardZ)
    var [x,y] = calcQandR(i,boardY)
    return [x,y,z,w]
}

for(let index=0; index < boardX * boardY * boardZ * boardW; index++){
    let [x,y,z,w] = index2pos(index)
    //処理
}

行数は増えましたが、大分すっきりしました。ただ、index2posの中に定数が残っていたりして、まだ使い勝手が向上できそうです。

解決策②:ジェネレータを使う

ジェネレータについて詳しいことは他の記事を参照してもらうとして、ここではfor-ofで取り出し可能な座標リスト(正確には「イテラブルなオブジェクト」)を生成するものと思ってもらえれば十分です。

function* product(...sizes){
    sizes = sizes.reverse()
    let all = sizes.reduce((a,c) => a*c, 1)
    for(let index=0; index<all; index++){
        let result = [], i = index
        sizes.forEach((size) => {
            result.push(i%size)
            i = Math.floor(i/size) 
        })
        yield result.reverse()
    }
}

for(let [x,y,z,w] of product(boardX,boardY,boardZ,boardW)){
    //処理
}

すっきりしました。何次元でも対応できるように書いたので、5次元や6次元になっても最小限の書き換えで対応できます。やったぜ。

pythonでは

itertoolsというパッケージがあり、その中のproductを使えば同じことが簡単に実現できます。jsでもこれが使えたらハッピーだねというだけの記事でした。

参考

JavaScript の ジェネレータ を極める!

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