転置行列を求める練習問題を解き、「Rubyの変数はラベル」の意味が理解できた話

こちらの問題を解いている時にハマったのでその備忘録です。

入力された行列の転置行列を求めよ

練習問題を毎日解いている背景はこちら。

qiita.com

考えた回答

最初に作ったのはこちらの回答。配列nをlにコピーしておいて、要素1つ1つの行と列を逆にして代入していく仕組み。

require 'minitest/autorun'

def tenchi(n)
  l = n
  n.length.times do |row|
    n.length.times do |col|
      l[row][col] = n[col][row]
    end
  end
  l
end

class TenchiTest < Minitest::Test
  def test_tenchi
    assert_equal [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]].transpose , tenchi([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]])
  end
end

しかし、これだとテストがうまく通らない。
記述しているロジックを紙に書いてみたりしたが、特に問題がないように思っていた。。

気づき

ロジックの考え方自体には問題がないのであれば、l = nのあたりに問題があるのではないかと、Rubyにおける変数の扱いについて調べてみた。

そこで分かったのがタイトルの通り「Rubyの変数はラベル」であること。

この記事が一番わかりやすかった。 gam0022.net

変数はそれ自体が一つの箱のようなイメージをしていたが、そうではなく、オブジェクトへの参照を示しているだけである。

するとどういうことが起きるかは以下が分かりやすい。

n = iを実施すると、iのさす配列を新しく作るのではなく、同じ配列に新しくnというラベルを付与する、という操作を行ったことになる。

つまり、同じオブジェクトを指しているので、iの値を変更した時に、同時にnの値も変更になることに注意が必要だ。

> i = [1, 2, 3]
> n = i
> n
=> [1, 2, 3]
> i[0] = 10
> i
=> [10, 2, 3]
> n
=> [10, 2, 3]

回答

その上で、最初に作成した回答を見てみると、上記のように、i = nで同じオブジェクトを参照してしまっていることになる。

ここの部分を、新規で空の配列を作るように変えてあげる。

l = Array.new(4).map{Array.new(4)}

するとあくまで新しい配列に値を代入していくだけなので、無事テストも通ることが確認できる。