LoginSignup
7
6

More than 5 years have passed since last update.

[Ruby] 二次元配列を列方向に足し合わせる(長さが異なる配列も可)

Last updated at Posted at 2014-08-01

やりたいこと

# [[1,1,1],[2,3],[4]] の場合、

[1, 1 ,1]
[2, 3    ]
[4        ]
 ↓↓↓ 列(タテ)方向に足し合わせる
[7, 4, 1]

もともと以下記事を参考に便利に使わせてもらっていたのですが、

Ruby - 配列の同じ項数同士を足す - Qiita
http://qiita.com/hash/items/7eb4d0eb865de719302d

この手法の場合、異なる長さの配列だとエラーになってしまいます。

OK(inputする各配列の長さが等しい)

# [1,1,1],[2,3,4] のinputで、[3,4,5] のoutputが欲しい
[13] pry(main)> [[1,1,1],[2,3,4]].transpose.map{|ary| ary.inject(&:+)}
=> [3, 4, 5]

NG(inputする各配列の長さが異なる)

# [1,1,1],[2,3],[4] のinputで、[7,4,1] のoutputが欲しい
[14] pry(main)> [[1,1,1],[2,3],[4]].transpose.map{|ary| ary.inject(&:+)}
IndexError: element size differs (2 should be 3)

ということで、対策を考えていきます。

追記

(2014/8/1 15:53 追記)
コメント欄にてよりベターな方法を教えて頂きました!

[42] pry(main)> array = [[1,1,1],[2,3],[4]]
=> [[1, 1, 1], [2, 3], [4]]
[43] pry(main)> array.each_with_object([]) { |e, a| e.each_index { |i| a[i] = a[i].to_i + e[i] } }
=> [7, 4, 1]

これにて、以下内容は不要となるので、参考程度に読み流して頂けますと・・。

TL;DR

以下の2ステップで完了です。

Arrayクラスをオープンしてsafe_transposeメソッドを追加

class Array
  def safe_transpose
    result = []
    max_size = self.max { |a,b| a.size <=> b.size }.size
    max_size.times do |i|
      result[i] = Array.new(self.first.size)
      self.each_with_index { |r,j| result[i][j] = r[i] }
    end
    result
  end
end

使う

[15] pry(main)> [[1,1,1],[2,3],[4]].safe_transpose.map{|ary|ary.inject(){|s,i|s=s+i.to_i}}
=> [7, 4, 1]

これで異なる長さの配列でも利用できるようになりました。

詳しく

transpose最適化

そもそもやろうとしているプロセスとしては、

  1. transposeで2次元配列を行列に見立てて、配列を列方向に入れ替え
  2. 各配列ごとの要素をinjectで足し合わせ

です。

そして今回のエラーの原因としては、
transposeが異なる長さの配列を許可していないから。

[16] pry(main)> [[1,1,1],[2,3],[4]].transpose
IndexError: element size differs (2 should be 3)

ということで、オープンクラスを用いてArrayクラスにsafe_transposeメソッドを定義します。

class Array
  def safe_transpose
    result = []
    max_size = self.max { |a,b| a.size <=> b.size }.size
    max_size.times do |i|
      result[i] = Array.new(self.first.size)
      self.each_with_index { |r,j| result[i][j] = r[i] }
    end
    result
  end
end

これで、足りない項の部分はnilで補ってくれるようになりました。

[14] pry(main)> [[1,1,1],[2,3],[4]].safe_transpose
=> [[1, 2, 4], [1, 3, nil], [1, nil, nil]]

inject最適化

本当はinject(&:+)で書きたいところですが、nilを数値にしてやる必要があるため(※)、こうなります。

[17] pry(main)> [[1,1,1],[2,3],[4]].safe_transpose.map{|ary|ary.inject(){|s,i|s=s+i.to_i}}
=> [7, 4, 1]

safe_transposeでnilでなく0を返せばinject(&:+)でいけるようになりますが、
その場合transposeの意図が変わってしまうため避けました。

もっとスマートなやり方もある気がするので、コメントお願い致します!

参考

How to safely transpose Ruby arrays
http://www.matthewbass.com/2009/05/02/how-to-safely-transpose-ruby-arrays/

Ruby - 配列の同じ項数同士を足す - Qiita
http://qiita.com/hash/items/7eb4d0eb865de719302d

7
6
2

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
7
6