QA@IT

行列を累積値に変換するためのよりよい方法はありますか?

2053 PV

以下のような行列があるとします。

matrix = [
[1,1,2,3,4],  
[2,1,2,3,4],  
[1,1,2,3,4],  
[1,1,2,3,4]  
]

これを以下のように各行を足していったものに変換するプログラムを書いているところです。

[
  [1, 1, 2, 3, 4], 
  [3, 2, 4, 6, 8], 
  [4, 3, 6, 9, 12], 
  [5, 4, 8, 12, 16]
]

以下のように書くとうまくいくのですが、もうちょっと簡潔な書き方は可能でしょうか?

accumulated = []
matrix.each_with_index do |rows, i|
  if i == 0
    accumulated << rows
  else
    new_rows = []
    rows.each_with_index do |cell, j| 
      new_rows << cell + accumulated[i - 1][j]
    end
    accumulated << new_rows 
  end
end

回答

reduceとzipを使うのはどうでしょうか?

accumulated = matrix.reduce([[0,0,0,0,0]]) do |s,c| 
  s << c.zip(s.last).map{|a| a.first + a.last}
end[1..-1]

reduce(またはinject)の基本的な使い方は以下の通りになります。初期値を指定することでif文で最初の行とそれ以外の行でロジックを換える必要がなくなります(最後に初期値をまた消す必要があるため[1..-1]しています)。

[1,2,3,4].reduce([0]){|s,c| s << s.last + c}
 => [0, 1, 3, 6, 10]

一つ前の行と現在の行を足す作業はzipで二つの行を結合した配列をつくり、さらにmapで両者を足しています。zipはblockをとるので"c.zip(s.last){|a| a.first + a.last}"としたいところですが、blockはnilしかかえさないので無理なようです。

[1,2,3].zip([4,5,6])
 => [[1, 4], [2, 5], [3, 6]] 

ちなみに二つの行の足し算はMatrixライブラリのVectorを使ってもできます。

require 'matrix'
Vector[1,2,3] + Vector[2,3,4]
 => Vector[3, 5, 7] 

Vectorを使って書き直すとこうなります。

require 'matrix'
new_matrix = Matrix.rows matrix
b = new_matrix.row_vectors.reduce([Vector[0,0,0,0,0]]) do |s,c| 
  s << [(s.last) + c].to_a
end[1..-1]
編集 履歴 (0)
ウォッチ

この質問への回答やコメントをメールでお知らせします。