二次元配列の[y][x]問題
二次元配列の座標が持つ問題について
二次元配列の[y][x]問題って?
二次元配列とはデータが二次元空間に配置されているデータ構造で、端的に言えば配列の中に配列を入れたものです。行列やグリッドなどを表現するのに一番シンプルな方法でしょう。
二次元配列は実装は簡単なのですが、多くのプログラマーが一度はひっかかる混乱ポイントがあります。配列の要素にアクセスする際の行と列の指定方法に関する問題です。
具体的にどういう問題なのか。例えば、入力された数字のグリッドに対して、各座標の数値にその上下左右の数値を足したグリッドを出力する処理を考えます(ちなみに枠外は0とします)。プログラミング問題では大抵最初の一行目に行数と列数が与えられ、その後にグリッドのデータが与えられます。
グリッドの各数字に上下左右の数字を足す
このコードには直感的ではない部分があります。数学などで二次元の座標を表すのに(x, y)という順番で表すことが多いため、配列のインデックスも[x][y]の順番でアクセスしたくなります。 しかし、二次元配列では最初のインデックスが行(y座標)、次のインデックスが列(x座標)であるため、grid[y][x]という形でアクセスする必要があります。
また、ループもxとyの順番で書きたくなりますが、二次元配列の各行にアクセスするためにはyを外側のループに、xを内側のループにする必要があります。 逆にすると正しい結果が得られません。
このような直感に反する実装はプログラマーに混乱をもたらし、バグの原因となることが多いです。
二次元配列の[y][x]問題の解決方法
配列のインデックスを[x][y]の順番でアクセスしたいなら、そうできるような仕組みにしてしまいましょう。
関数、クラスなどなど様々な手段が考えられます。ここではMapオブジェクトを使った実装を紹介します。
グリッドの各数字に上下左右の数字を足す(Map版)
キーを"x,y"の形式で保存することで、インデックスをx, yの順番でアクセスできるようになりました。これにより、より直感的な順番でアクセスできるようになります。
Mapを使うことでメモリ効率が悪くなる可能性がありますが、コードの可読性や保守性を向上させることができます。特にチーム開発では、他の開発者がコードを理解しやすくなることが重要です。ちなみにグリッドが疎な場合(二次元空間に対して非ゼロの値が少ない場合)ではMapの方がメモリ効率が良くなる場合もあります。
ループの順番は?
ループの順番が外側がy、内側がxであることには変わりありません。この問題の解決方法は……私には思いつきません。
これは入力が行ごとに与えられ、行ごとに出力するアルゴリズム上の制約です。例えば別のアルゴリズムではまたループの順番が違う方が効率的かもしれません。こればかりは仕方がないと割り切るしかないでしょう。
まとめ
二次元配列の[y][x]問題について説明しました。配列のインデックスの順番が直感に反するため、混乱を招くことが多いです。この問題を解決するために、Mapオブジェクトを使ってインデックスを[x][y]の順番でアクセスできるようにしました。
ループの順番については避けられない制約があるため、割り切るしかないでしょう。二次元配列を扱う際には、この問題を意識して実装することが重要です。