Ruby初心者向けプログラミング問題10選を解いてみた(下)

[Ruby初心者向けプログラミング問題10選を解いてみた(上)](https://qiita.com/porryanes/items/525e3ef5b4a7e40699fc)の続きです
引き続き自分の回答を貼っていきますが、2問解けてないです（へなちょこ）

# 素晴らしき元サイト様
[アウトプットのネタに困ったらこれ！？Ruby初心者向けのプログラミング問題を集めてみた（全10問）](https://blog.jnito.com/entry/2019/05/03/121235)

# 国民の祝日.csv パースプログラム (回答無し)
## 問題文
<details><summary>問題文(折りたたみ)</summary><div>
>その昔、「国民の祝日.csv」という扱いづらいCSVが話題になっていました。
具体的にはこんなCSVファイルです↓
>
```
平成28年（2016年）,,平成29年（2017年）,,平成30年（2018年）,
名称,月日,名称,月日,名称,月日
元日,2016/1/1,元日,2017/1/1,元日,2018/1/1
成人の日,2016/1/11,成人の日,2017/1/9,成人の日,2018/1/8
建国記念の日,2016/2/11,建国記念の日,2017/2/11,建国記念の日,2018/2/11
春分の日,2016/3/20,春分の日,2017/3/20,春分の日,2018/3/21
昭和の日,2016/4/29,昭和の日,2017/4/29,昭和の日,2018/4/29
憲法記念日,2016/5/3,憲法記念日,2017/5/3,憲法記念日,2018/5/3
みどりの日,2016/5/4,みどりの日,2017/5/4,みどりの日,2018/5/4
こどもの日,2016/5/5,こどもの日,2017/5/5,こどもの日,2018/5/5
海の日,2016/7/18,海の日,2017/7/17,海の日,2018/7/16
山の日,2016/8/11,山の日,2017/8/11,山の日,2018/8/11
敬老の日,2016/9/19,敬老の日,2017/9/18,敬老の日,2018/9/17
秋分の日,2016/9/22,秋分の日,2017/9/23,秋分の日,2018/9/23
体育の日,2016/10/10,体育の日,2017/10/9,体育の日,2018/10/8
文化の日,2016/11/3,文化の日,2017/11/3,文化の日,2018/11/3
勤労感謝の日,2016/11/23,勤労感謝の日,2017/11/23,勤労感謝の日,2018/11/23
天皇誕生日,2016/12/23,天皇誕生日,2017/12/23,天皇誕生日,2018/12/23
,,,,,
月日は表示するアプリケーションによって形式が異なる場合があります。,,,,,
```
>(中略)
>これをいい感じにパースして、以下のようなデータ構造（Rubyのハッシュオブジェクト）に変換しよう、というプログラミング問題です。
>
```Ruby
{
  2016 => {
    # 実際のキーは文字列ではなくDateオブジェクト
    '2016/01/01' => '元日',
    '2016/01/11' => '成人の日',
    # ...
    '2016/11/23' => '勤労感謝の日',
    '2016/12/23' => '天皇誕生日',
  },
  2017 => {
    '2017/01/01' => '元日',
    '2017/01/09' => '成人の日',
    # ...
    '2017/11/23' => '勤労感謝の日',
    '2017/12/23' => '天皇誕生日',
  },
  2018 => {
    '2018/01/01' => '元日',
    '2018/01/08' => '成人の日',
    # ...
    '2018/11/23' => '勤労感謝の日',
    '2018/12/23' => '天皇誕生日',
  },
}
```
</div></details>

## コード
csvの扱い方が全くわからなかったので解けなかったです…
代わりと言ってはなんですが本家の模範解答のURLを貼っておきます
[【短命に終わった】国民の祝日.csvをパースして変換するRubyプログラムとコード解説動画](https://qiita.com/jnchito/items/b8a2ed3544c1dc36fb9d)

## 要点解説
解説動画です
[【短命に終わった】国民の祝日.csvをパースして変換するRubyプログラムとコード解説](https://www.youtube.com/watch?v=h0WlWPfQ0Sw)

# 「Rubyで英語記事に含まれてる英単語を数えて出現数順にソートする」問題
## 問題文
<details><summary>問題文(折りたたみ)</summary><div>
>テキストファイルの中から英熟語や英単語を抜き出してカウントする、という問題です。

>入力に使うテキストファイルは[こんな感じ](https://github.com/JunichiIto/extract-word-sandbox/blob/master/test/expected_output.txt)です。
>
```
Interior design and decorating resource Houzz is the overall best Android app of the year, according to Google, which this evening announced the results of the first-ever Google Play Awards at its developer conference, Google I/O. 
（略）
```
>出力結果は[こんな感じ](https://github.com/JunichiIto/extract-word-sandbox/blob/master/test/expected_output.txt)になります。
>
```
単語数（熟語以外）：331
英熟語？------------------------------------------------------------------
  2 Google I/O
  2 Google Play Awards
  1 And Google
  1 Best App
  1 Best Game
  1 Best of
  1 Best Standout Startup
  1 Best Use of Google Play Game Services
  1 Best Use of Material Design
  (略)
英単語------------------------------------------------------------------
 22 the
 11 and
 11 of
  8 a
  6 apps
  5 app
  5 best
  5 for
  5 Google
  5 to
  4 that
  4 this
  (略)
```
</div></details>

## コード
*かなりゴチャゴチャしてます
*熟語の判定がガバガバなので本家と出力結果が違います

```Ruby
file_pass = # 任意のパス
sentence = File.read(file_pass)
words = []
idioms = []

# 英熟語を抜き出す
while (idiom = sentence.slice!(/[A-Z]\w*(?:[\/\s-]+[A-Z]\w*)+/))
  idioms << idiom
end
idioms_hash = idioms.tally

# 記号系を消す
while (s = sentence.slice!("’s"))
  words << s
end
sentence.gsub!(/[,.“”?!；:ー"–]/, " ")

# 単語を抜き出す
words << sentence.split
words.flatten!

words_hash =  Hash.new(0)
words.each { |word| words_hash[word.downcase] += 1}

# ソート
idioms_hash = idioms_hash.sort_by { |idiom, freq| [-freq, idiom] }.to_h
words_hash = words_hash.sort_by { |word, freq| [-freq, word] }.to_h

# 出力
puts "単語数 : #{words.count + idioms.count}"
puts "英熟語?-----------------------------------------------------------------"
idioms_hash.each { |word, num| puts "#{num} #{word}" }
puts "英単語------------------------------------------------------------------"
words_hash.each { |word, num| puts "#{num} #{word}" }

```

## 要点解説
```Ruby
while (idiom = sentence.slice!(/[A-Z][\w]*(?:[\/\s-]+[A-Z][\w]*)+/))
  idioms << idiom
end
```
`(idiom = sentence.slice!(/[A-Z][\w]*(?:[\/\s-]+[A-Z][\w]*)+/))`は条件式の中で警告を出さずに変数代入を行う書き方です（カッコを外すと警告が出ます）

```Ruby
idioms_hash = idioms_hash.sort_by { |idiom, freq| [-freq, idiom] }.to_h
words_hash = words_hash.sort_by { |word, freq| [-freq, word] }.to_h
```
`sort_by`メソッドでは、レシーバの各要素を`<=>`で比較します
配列を`<=>`で比較すると、各要素を順番に比較し0でない値が出た時その値を返します
なので、`[-freq, idiom]`と書くとまず`freq`の値でソートし、もし同じ値だったらidiomをアルファベット順に並べる、ということができます

sortの結果は配列になっているので`to_h`でハッシュに戻します（執筆中に気づきましたがここは配列のままでも動作するみたいです）

# 行単位、列単位で合計値を求めるプログラム
## 問題文
<details><summary>問題文(折りたたみ)</summary><div>
>行単位、列単位で合計値を出しましょう、という問題です。

>たとえばこういうインプットであれば、
>
| -    | col1 | col2 | col3 | col4 |
|------|------|------|------|------|
| row1 | 9    | 85   | 92   | 20   |
| row2 | 68   | 25   | 80   | 55   |
| row3 | 43   | 96   | 71   | 73   |
| row4 | 43   | 19   | 20   | 87   |
| row5 | 95   | 66   | 73   | 62   |

>こういう結果になります。
>
| -    | col1 | col2 | col3 | col4 | sum  |
|------|------|------|------|------|------|
| row1 | 9    | 85   | 92   | 20   | 206  |
| row2 | 68   | 25   | 80   | 55   | 228  |
| row3 | 43   | 96   | 71   | 73   | 283  |
| row4 | 43   | 19   | 20   | 87   | 169  |
| row5 | 95   | 66   | 73   | 62   | 296  |
| sum  | 258  | 291  | 336  | 297  | 1182 |

>ただし、出題元のブログでは以下のような仕様になっていました。

>- ランダムに数字を出力する
>- 計算結果は以下のようなフォーマットで出力する
>
````text
   9|  75|  83|  74| 241
   0|  27|  32|  48| 107
  51|  66|  76|   3| 196
   2|  37|  69|  85| 193
  55|  40|  25|  88| 208
 117| 245| 285| 298| 945
````
</div></details>

## コード
```Ruby
class SumMatrix
  class << self
    def sum
      matrix = generete_matrix
      matrix = sum_matrix(matrix)
      format_matrix(matrix)
    end

    def generete_matrix
      Array.new(5) { Array.new(4) { rand(100) } }
    end

    def sum_matrix(matrix)
      2.times do
        matrix.each { |ary| ary << ary.sum }
        matrix = matrix.transpose
      end
      matrix
    end

    def format_matrix(matrix)
      matrix.each { |elem| elem.map! { |x| x.to_s.rjust(4) } }
      matrix.map! { |elem| elem.join("|") }
      matrix.join("\n")
    end
  end
end
```

## 要点解説
```Ruby
class << self
```
endまでで定義したメソッドがクラスメソッドとして定義されます
ネストは増えるけど便利

```Ruby
      Array.new(5) { Array.new(4) { rand(100) } }
```
`Array.new`は第1引数に要素数、第2引数に中身を指定してデフォルト値(?)を作れます
しかし、出来上がった配列の中身はすべて同じオブジェクトを参照しているのでうまく動きません
なので別々のオブジェクトを与えるためブロックを使っています

また、常に重複した値が出る可能性があるようにしています
このコードを教えてくださったコメント欄の@scivolaさんに感謝

```Ruby
      matrix.each { |elem| elem.map! { |x| x.to_s.rjust(4) } }
```
例えば`[1,2], [3, 4]`を`[["   1", "   2"], ["   3", "   4"]]`にします
ここはもうちょっといい書き方があった気がします

# ガラケー文字入力問題
## 問題文
<details><summary>問題文(折りたたみ)</summary><div>
>英語のガラケーでは「2」キーを2回押すと「b」になり、「3」キーを3回押すと「f」になります。
>ただし、この問題では特別ルールとして「0」で文字を確定させます。

>たとえば、このプログラムに対して"440330555055506660"を入力すると、"hello"が返ってきます。
</div></details>

## コード
*keitai_messageの引数はStringとして取っていますが、これはIntegerとするとメソッド呼び出しのとき8進法の数と解釈されてエラーを起こすことがるからです
*入力を10進法のリテラルにするのも違うかな、と考えこうしました

```Ruby
CHARSES = {
  "1" => %w(. , ! ? \ ),
  "2" => %w(a b c),
  "3" => %w(d e f),
  "4" => %w(g h i),
  "5" => %w(j k l),
  "6" => %w(m n o),
  "7" => %w(p q r s),
  "8" => %w(t u v),
  "9" => %w(w x y z)
}.freeze

def keitai_message(message)
  nums = message.scan(/([123456789]+)0/).flatten
  return if nums.empty?

  nums.map! do |num|
    chars = CHARSES[num.chr]
    index = (num.size % chars.size) - 1
    chars[index]
  end
  nums.join
end

```

## 要点解説
```Ruby
nums = message.scan(/([123456789]+)0/).flatten
```
scanの引数の正規表現にカッコが含まれる場合、カッコの内側にマッチした文字列が配列になって返ります
つまり、"01022"は[["1"], ["22"]]になるわけですね

# 値札分割問題 (回答無し)
## 問題文
<details><summary>問題文(折りたたみ)</summary><div>
>「値段らしき文字列」を「数字部分」と「単位部分」に分割する、split_priceメソッドを作ってください、というプログラミング問題です。
>
```Ruby
split_price '110.0万円'     #=> ['110.0', '万円']
split_price '2015円'        #=> ['2015', '円']
split_price '1,123,456円'   #=> ['1,123,456', '円']
split_price '110 - 120万円' #=> ['110 - 120', '万円']
split_price '２０１５円'    #=> ['２０１５', '円']
split_price '価格未定'      #=> ['価格未定', '']
split_price nil             #=> ['', '']
```
</div></details>

## コード
全然わかりませんでした…!
なので本家の模範解答のURLを貼っておきます
[【Rubyプログラミング問題】値札分割メソッド(split_price)を作成してください：解答編](https://qiita.com/jnchito/items/c460bff5aec851ed36f9#%E5%84%AA%E7%A7%80%E8%B3%9E%E3%81%AE%E7%99%BA%E8%A1%A8)

# おわり
以上、「Ruby初心者向けプログラミング問題10選を解いてみた」でした
ここまで読んでいただき本当にありがとうございました！