やりたいこと
# [[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最適化
そもそもやろうとしているプロセスとしては、
- transposeで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