多次元配列であれこれ 続き

2次元の配列でビットマップをまねて、左右対称に反転/上下対称に反転 を再現する操作 の続きをやります。
今度は2次元の配列(行列?平面?)を「90度回転」する操作をしてみようと思います。画像を90度回転する ってよくやるよね。写真の縦横の向きを変えたりとか。その操作の再現。

っとその前に前日のコードのリファクタリングからやります。

my @A = qw(a1 a2 a3 a4);
my @B = qw(b1 b2 b3 b4);
my @C = qw(c1 c2 c3 c4);
my @D = qw(d1 d2 d3 d4);

my $XY = [ \(@A , @B , @C , @D) ];

配列は全部無名配列にしてリファレンスとしてぶっ込みました。入れ子になったこの配列をこれからごにょごにょします。

配列に対する操作は

sub y_reverse {
  my $ref = shift;
  @{$ref} = reverse @{$ref};
  return $ref;
}

sub x_reverse {
  my $ref = shift;
  foreach(@{$ref}){
   @{$_} = reverse @{$_};  
  }
  return $ref;
}

としてサブルーチン化して、x_reverse()で左右対称に反転、y_reverse()で上下対称に反転をするようにしました。サブルーチンには2次元配列のリファレンス($XY)を渡します。

これでコードを再利用できますね。




さて前置きはここまで。2次元の無名配列を90度回転しようと思います。元の配列は

#オリジナル
a1 a2 a3 a4
b1 b2 b3 b4
c1 c2 c3 c4
d1 d2 d3 d4

となっていますが、これを時計回り(→↓)に90度回転して

#こんな風にしたい!
d1 c1 b1 a1
d2 c2 b2 a2
d3 c3 b3 a3
d4 c4 b4 a4

になってほしい。これを実現するためのロジックが↓

# $dec => $decrement 配列の大きさの最大値 を デクリメントする変数//今回は 3 -> 2 -> 1 ->0  
# $inc => $increment 0からインクリメントする変数                 //今回は 0 -> 1 -> 2 ->3
# $length は(配列の大きさ-1) // 今回は 3 

sub turn_90 {
  my $ref = shift;
  my $copy;
   
  for(0..$length){
    my $tmp = $_;
    my $inc = 0;
    my $dec = $length;
    for(0..$length){
      $copy->[$tmp]->[$dec] = $ref->[$inc]->[$tmp];
      $dec--;
      $inc++;
    }
  }
  return $copy;
}

こんなコードになりました。

  • $refは もともとの2次元の配列のリファレンス。
  • $copy は 90度回転した2次元の配列を保持します。

for文が入れ子になっていて、さらにインクリメントする変数とデクリメントする変数、配列の長さの分だけforを繰り返す変数が混じっているので かなりややこしいサブルーチンになってしまいました。変数を三つ使わないと、配列を入れ替える操作ができませんでした。また、新たな配列を作ってその配列に対して結果を代入していかないと90度回転は実現できないようです。(オリジナルの配列で操作しようとすると どうしても無理が生じる)

最初、変数を二つでfor文を回そうとしたら上手くいかなくて、あれこれ一晩も悩んでようやく答えに行き着きました。もうちょっと詳しく説明したいけど、複雑でどう説明したらいいんだろ・・・ギブアアプ。分かんないなーという人は for文のループをひとつひとつ試して、配列の要素が移動する図を書いてみてください。


このサブルーチンでData::Dumperすると

$VAR1 = [
          ['d1','c1','b1','a1'],
          ['d2','c2','b2'.'a2'],
          ['d3','c3','b3','a3'],
          ['d4','c4','b4','a4']
        ];

となり、オリジナルの配列が時計回りに90度回転させた形になっているので、うまく動いています。(出力は整形してます)。同じ2次元配列に対してこのサブルーチンを4回作用させると、360度回転して元に戻ります。いい感じ。


今回は 配列の大きさが固定してあったし、かつ、「平面」の縦横の大きさが一緒でした(正方形)。なので、あまり多くのことを考えずにうまくいったけど、縦横の大きさが違う場合の変形操作は また いろんなことを考慮しないといけないっぽい。さらに30度回転、45度回転、60度回転、をやろうとすると かなり複雑になる感じです。。。。