行単位、列単位の合計値算出プログラムを実装する


# はじめに

[Ruby初心者向けのプログラミング問題を集めてみた](https://blog.jnito.com/entry/2019/05/03/121235)の問題を解いてみました。
今回は、Excelのように、行・列の格子状になるランダムな数値を出力し、行・列単位で合計値を算出し、出力するプログラムにチャレンジしました。

最終的には、以下のような形式にフォーマットしたいです。

```
   9|  75|  83|  74| 241
   1|  27|  32|  48| 108
  51|  66|  76|   3| 196
   2|  37|  69|  85| 193
  55|  40|  25|  88| 208
 118| 245| 285| 298| 945
```

## 要求仕様

- 計算のベースとなる配列を作成する
  - フォーマットし出力した際に、5 × 4 となるような配列であること
  - 配列の値は`1〜99`のランダムな値であること
- ベースとなる配列を元に、行・列で合計値を算出し、例の通りフォーマットされた表を出力する
  - 文字列で出力すること

# 自分が書いたソースコード

```ruby
class SumMatrix
  def call
    srand(5)
    sum_rows_array = []
    sum_columns_array = []
    result = ''

    calculate_row(sum_rows_array)
    calculate_column(sum_rows_array, sum_columns_array)

    format_matrix(sum_rows_array << sum_columns_array, result)
    result
  end

  private

  def calculate_row(sum_rows_array)
    5.times do
      base_array = []
      4.times do
        base_array << rand(100)
      end
      base_array << base_array.sum
      sum_rows_array << base_array
    end
  end

  def calculate_column(sum_rows_array, sum_columns_array)
    sum_rows_array.size.times do |number|
      base_array = []
      sum_rows_array.each do |array|
        base_array << array[number]
      end
      sum_columns_array << base_array.sum
    end
  end

  def format_matrix(matrix_array, result)
    matrix_array.each do |array|
      array.each_with_index do |number, index|
        string = if index == array.size - 1
                   "#{number}\n"
                 else
                   "#{number}|"
                 end.rjust(5, ' ')
        result << string
      end
    end
  end
end
```

## 1. 行・列のランダムな数値を作成


```ruby
  def calculate_row(sum_rows_array)
    5.times do
      base_array = []
      4.times do
        base_array << rand(100)
      end
      base_array << base_array.sum
      sum_rows_array << base_array
    end
  end
```

### 処理について

`calculate_row`メソッドで、ランダムな整数からなる5 × 4の配列を作成し、行の合計値を算出しています。
`times`メソッドを使用して5 × 4の配列を作成しています。

### 改善点

まず、メソッド呼び出しで引数の状態が変わることはあまり良くないことですが、引数の`sum_rows_array`に対して、破壊的に変更を加えてしまっています。

また、`base_array`で空配列を定義していますが、mapを使うと`base_array`は不要になりますね。
引数で`sum_rows_array`の空配列を受け取っていますが、こちらもmapを使用すると引数は不要になります。


`rand(100)`としていますが、この場合だと`0~99`のランダム整数が生成されてしまうため、要件(`1~99`)と異なってしまっています。
要件を満たすためには、`rand(1..99)`のように範囲オブジェクトを使用するべきでした。

`5`や`4`等の決まった値は定数化が可能でした。


## 2. 行・列で合計値を算出する

```ruby
  def calculate_column(sum_rows_array, sum_columns_array)
    sum_rows_array.size.times do |number|
      base_array = []
      sum_rows_array.each do |array|
        base_array << array[number]
      end
      sum_columns_array << base_array.sum
    end
  end
```

### 処理について

`calculate_column`メソッドで、列の合計値、また行・列の合計値を算出しています。

### 改善点

こちらも`calculate_row`メソッドと同様で、引数に対して破壊的に変更を加えてしまっています。

また、`times`メソッドではなく、`each`でループを回すだけでよかったと思います。
また、`sum_rows_array`の`size`を使用していますが、事前に定数化しておくと可読性が上がったと思います。


## 3. フォーマットする

```ruby
  def format_matrix(matrix_array, result)
    matrix_array.each do |array|
      array.each_with_index do |number, index|
        string = if index == array.size - 1
                   "#{number}\n"
                 else
                   "#{number}|"
                 end.rjust(5, ' ')
        result << string
      end
    end
  end
```

### 処理について

作成した配列を、指定の形式の文字列にフォーマットします。

### 改善点

`each_with_index`に引数を設定していませんが、`each.with_index(1)`のように、`with_index`で初期値を`1`で設定すると`array.size - 1`しなくても良くなるため、こちらも可読性が上がったと思います。

# 回答例

先輩にいただいた回答例です。

```ruby
class SumMatrix
  NUMBER_RANGE = 1..99
  ROW_LENGTH = 5
  COL_LENGTH = 4
  SEPARATE_STRING = '|'.freeze

  def generate_matrix
    Array.new(ROW_LENGTH) { NUMBER_RANGE.to_a.sample(COL_LENGTH) }
  end

  def sum_matrix(matrix)
    row_total_array = matrix.map { |row| row + [row.sum] }
    col_total_array = row_total_array.transpose.map(&:sum)

    row_total_array + [col_total_array]
  end

  def format_matrix(matrix)
    matrix.map(&method(:format_row)).join("\n")
  end

  private

  def format_row(row)
    row.map(&method(:format_number)).join(SEPARATE_STRING)
  end

  def format_number(number)
    format('%4d', number)
  end
end
```

## 1. 定数化する

```ruby
  NUMBER_RANGE = 1..99
  ROW_LENGTH = 5
  COL_LENGTH = 4
  SEPARATE_STRING = '|'.freeze
```

まず、決まった値は定数化されています。
定数化するメリットは以下になります。

- 定数名が付くので、その数字が何を意味しているのかが分かり易い。
- （複数箇所で利用している場合）改修のコストが下がる。（修正漏れによるデグレ回避が可能）

## 2. 行・列のランダムな数値を作成

```ruby
  def generate_matrix
    Array.new(ROW_LENGTH) { NUMBER_RANGE.to_a.sample(COL_LENGTH) }
  end
```

1行で5 × 4の配列が作成されています。

配列の長さに指定がある場合は、`Array.new`を使用すると良いことが分かりました。

- `Array.new(size) {|index| ... }`
  - 長さ`size`の配列を生成し、各要素の`index`を引数としてブロックを実行し、各要素の値をブロックの評価結果に設定します。

ブロック内では、`to_a`で配列に変換し、`sample`メソッドを使用されています。

- `sample(n)`
  - 配列から`n`個の要素をランダムに選んで返します。
  - 重複した`index`は選択されません。

## 3. 行・列で合計値を算出する

```ruby
  def sum_matrix(matrix)
    row_total_array = matrix.map { |row| row + [row.sum] }
    col_total_array = row_total_array.transpose.map(&:sum)

    row_total_array + [col_total_array]
  end
```

`sum_matrix`の引数に5 × 4の配列を渡すと、行・列の合計値が算出されます。

`row_total_array`に対して`transpose`し、それぞれの配列で合計値を出しているところがポイントと思いました。

- `transpose`
  - ネストされた配列に対して、行と列の入れ換えを行います。

(`transpose`の使用例)

```ruby
p [[1,2],
   [3,4],
   [5,6]].transpose
# => [[1, 3, 5], [2, 4, 6]]
```

また、`sum_matrix`の別の方法もメモしておきます。
([伊藤淳一さんの回答](https://qiita.com/jnchito/items/2fedbfa9f887635e6c6a)をお借りしました。)

```ruby
  def sum_matrix(matrix)
    matrix
        .map{|row| [*row, row.inject(:+)] }
        .transpose
        .map{|row| [*row, row.inject(:+)] }
        .transpose
  end
```

こちらは、配列展開を使用されています。
`*row`で配列(`row`)を展開し、`inject`メソッドが実行されています。

## 4. フォーマットする

```ruby
  def format_matrix(matrix)
    matrix.map(&method(:format_row)).join("\n")
  end

  private

  def format_row(row)
    row.map(&method(:format_number)).join(SEPARATE_STRING)
  end

  def format_number(number)
    format('%4d', number)
  end
```

`format_matrix`の引数に完成した配列を渡すと、フォーマットされた文字列が返ります。

`&method(:メソッド名)`で、配列の要素１つ１つに対して、`method`で与えられたメソッドを呼んでいます。
`&method`を使わずに書くと以下のようになります。

```ruby
row.map { |number| format_number(number) }
```
`&method`を使用すると、`proc`が省略できるため、簡潔に書けて良いですね。

`format`メソッドについてもメモしておきます。

- `format(format, *arg)`
  - `format`: フォーマット文字列(変換したい書式を指定する。)
  - `arg`: フォーマットされる値

今回は、`%4d`で4桁に桁数が揃えられています。

# 学んだこと

- 要件を意識する。
  - `srand(5)`で結果が固定値になっていたり、ランダムな数値の範囲が異なってしまっていたこと等、要件を見たせていないことがあったため、要件を理解し、満たすようにする。
- テストファーストを心がける。
  - RSpecを先に書くことで、要件を満たすプログラムを作成することが出来る。
- 変数や引数に渡したものに対して、破壊的メソッドは(基本的に)使わない。
  - 本当に破壊的メソッドを使うしかないのか、処理を見返してみる。
- 決まった値は、定数化する。
  - 1箇所で記載されているため、改修コストが下がる。
- `times`メソッドはあまり使わない。
  - `map`や`each`で出来ないか、処理を見返してみる。
- 空配列を定義することはあまりない。
  - `map`で出来ないか、処理を見返してみる。

# まとめ

Rubyには便利なメソッドが多くあるので、今後はメソッドの使いどころを意識して、適切なタイミングで活用できるようになりたいと思います。