Ruby初心者向けのプログラミング問題を集めてみた（全10問）の問題を解いてみた(前編)

# はじめに
[アウトプットのネタに困ったらこれ！？Ruby初心者向けのプログラミング問題を集めてみた（全10問）](https://blog.jnito.com/entry/2019/05/03/121235)
の問題を解いてみました。(前編)

現在[チェリー本](https://gihyo.jp/book/2017/978-4-7741-9397-7)でRubyの勉強をしているのですが、著者である伊藤淳一さんがRuby初心者向けに上記の記事を投稿されたので、実力アップのために解いてみました。

問題は以下の通りです。
この記事では１〜5まで解いています。
6〜10を解いた後編は[こちら](https://qiita.com/sackey_24/items/d03572807470373bc9cf)

1. カレンダー作成問題
2. カラオケマシン問題
3. ビンゴカード作成問題
4. ボーナスドリンク問題
5. 電話帳作成問題
6. 国民の祝日.csv パースプログラム
7. 「Rubyで英語記事に含まれてる英単語を数えて出現数順にソートする」問題
8. 行単位、列単位で合計値を求めるプログラム
9. ガラケー文字入力問題
10. 値札分割問題

# 追記
2019/05/12 頂いたコメントを参考に、1,3,5のコードを編集

# 1. カレンダー作成問題
[記事](https://blog.jnito.com/entry/2019/05/03/121235)より引用。
> [Dateクラス](https://docs.ruby-lang.org/ja/latest/class/Date.html)を使って、今月の１日と月末の日付と曜日を求め、次のような形式でカレンダーを表示させてください。

>```
 April 2013
 Su Mo Tu We Th Fr Sa
     1  2  3  4  5  6
  7  8  9 10 11 12 13
 14 15 16 17 18 19 20
 21 22 23 24 25 26 27
 28 29 30
```

作成したコード

```ruby:
require 'date'

head = Date.today.strftime("%B %Y")  # 今日の月と西暦を取得
year = Date.today.year
mon = Date.today.mon
firstday_wday = Date.new(year,mon,1).wday   # 今月1日の曜日を取得(0~6)
lastday_date = Date.new(year,mon,-1).day    # 今月の最終日を取得
week = %w(Su Mo Tu We Th Fr Sa)

puts head.center(20)        # 月と西暦中央寄せで出力
puts week.join(" ")         # 曜日を出力
print "   " * firstday_wday # 1日までの空白を出力

wday = firstday_wday
(1..lastday_date).each do |date|    # 1~最終日まで繰り返し
  print date.to_s.rjust(2) + " "    # 日付を右寄せで表示
  wday = wday+1
  if wday%7==0                      # 土曜日まで表示したら改行
    print "\n"
  end
end
if wday%7!=0
  print "\n"
end
```

`Date.new(year,mon,-1).day`で今月の最終日を取得し、1日から最終日までループを回して日付を出力しています。
また、`Date.new(year,mon,1).wday`で今月の初日の曜日(0~6)を取得しています。
この数字は0~6で日~土を表しています。この曜日の情報を利用して、日付を出力する位置を調整しています。

# 2. カラオケマシン問題
問題：[CodeIQに「カラオケマシン問題」を出題しました。みんなチャレンジしてね！](https://blog.jnito.com/entry/2014/06/06/104420)
テストコード：[https://gist.github.com/JunichiIto/c548e39fed60bf4bd36a]
(https://gist.github.com/JunichiIto/c548e39fed60bf4bd36a)

メロディーとキー(+2とか-4とか)が与えられるので、キー変更後のメロディーを返すメソッドを作るという問題です。

作成したコード

```ruby:
class KaraokeMachine
  def initialize(melody)
    @melody=melody.reverse                    # メロディーを反転させる
  end

  def transpose(key)
    keys = %w(C C# D D# E F F# G G# A A# B)   # キーを配列で持っておく
    downkey = key % (-12)                     # keyを(-12)で割った余りを持つことで、
    newmelody=""                              # キーの変換を引き算で行うことができる
    pre=""
    flg = false
    @melody.each_char do |m|                  # メロディーを後ろから1文字ずつ見ていく
      if m=='#'                               # '#'が現れたらその次の一文字と連結させる
        flg = true
        next
      end
      if flg
        m=m+'#'
        flg =false
      end
      if keys.include?(m)                     # 文字列mがキーを表していたら、キーを変換する
        newkey = keys.index(m)+downkey
        newmelody = keys[newkey] + newmelody
      else                                    # 文字列mがキーで無ければそのまま
        newmelody = m + newmelody
      end
    end
    
    return newmelody
  end
end
```
受け取ったキーを(-12)で割った余りを計算することによって、キーの上げ下げを引き算で行えるように変換しています。
例えば以下のように変換されます。

```irb:
irb(main):004:0> 4 % (-12)     # キー+4 => キー-8
=> -8
irb(main):005:0> 12 % (-12)    # キー+12 => キー-0
=> 0
irb(main):006:0> -13 % (-12)   # キー-13 => キー-1
=> -1
```
これにより、キーの配列["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"]に対して、変換前のキーの添字と、上記で計算した値の和が、変換後のキーの添字になります。(配列の添字が負になると循環するから)。

この変換を用いて、メロディーを先頭から一文字ずつ見ていって変換したところ、テストが通らず。
原因は、メロディーが"F# CBAF"みたいなときに、Fが現れた時点でFを変換してしまっていたからです。
そこで、受け取ったメロディーをreverseで反転させて後ろから見ていき、#が現れたらその次の一文字と連結させて、キーを変換しました。これによって、F#が現れたときにFだけを変換してしまう、という間違いを防いでいます。

# 3. ビンゴカード作成問題
問題：[CodeIQに「ビンゴカード作成問題」を出題しました。みなさんの挑戦をお待ちしてます！](https://blog.jnito.com/entry/2015/02/09/101018)
テストコード：[https://gist.github.com/JunichiIto/5d4c63150d5ad48b5bf8](https://gist.github.com/JunichiIto/5d4c63150d5ad48b5bf8)

以下のようなビンゴカードを作成するという問題

```
 B |  I |  N |  G |  O
13 | 22 | 32 | 48 | 61
 3 | 23 | 43 | 53 | 63
 4 | 19 |    | 60 | 65
12 | 16 | 44 | 50 | 75
 2 | 28 | 33 | 56 | 68
```

作成したコード

```ruby:
class Bingo
  def self.generate_card
    b = (1..15).to_a.sample(5)        # 1..15からランダムに5つ選んで配列を作成
    i = (16..30).to_a.sample(5)
    n = (31..45).to_a.sample(5)
    n[2] = ""                         # 真ん中をfreeに
    g = (46..60).to_a.sample(5)
    o = (61..75).to_a.sample(5)

    card = " B |  I |  N |  G |  O\n"
    5.times do |j|
      [b,i,n,g,o].each do |column|
        card += column[j].to_s.rjust(2) + " | "         # 一行ずつ連結
      end
      card[-3..-1]="\n"               # 右端の' | 'を改行に置換
    end
    return card
  end
end
```

列ごとに決まった範囲の数字を並べた配列を作成して、そこから`sample`によってランダムに5つ取り出して並べました。

# 4. ボーナスドリンク問題
[サイト](https://blog.jnito.com/entry/2019/05/03/121235)より引用。
>「ある駄菓子屋で飲み物を買うと、空き瓶3本で新しい飲み物を1本プレゼントしてくれる。最初に100本購入した場合、トータルで何本飲めるか」という小学校3年生の算数の問題をベースにしたプログラミング問題です。
>
| 購入した本数  |   飲める本数       |
|:----------:|:----------------:|
| 0           |         0       |
| 1           |         1       |
| 3           |         4       |
| 11          |        16       |
| 100         |(プログラムで算出する)|



作成したコード

```ruby:
def count_additional_bottle(n)             # 空き瓶の本数を受け取って追加で飲める本数を返す
  if n<3
    0
  else
    n/3 + count_additional_bottle(n/3+n%3)
  end
end

def count_bottle(n)
  n + count_additional_bottle(n)           # 最初に飲んだ本数と、追加で飲める本数の和を計算
end

puts count_bottle 0     # => 0
puts count_bottle 1     # => 1
puts count_bottle 3     # => 4
puts count_bottle 11    # => 16
puts count_bottle 100   # => 149
```
問題文の操作をそのまま再帰関数で記述しました。
後で調べてみたら、`n+(n-1)/2`で良いみたいですね。
小学校3年生からやり直します。

# 5. 電話帳作成問題


```ruby:
def name_index names
  katakana = [
    [*'ア'..'オ']<<'ヴ',          # カタカナを行ごとに格納
    [*'カ'..'ゴ'],
    [*'サ'..'ゾ'],
    [*'タ'..'ド'],
    [*'ナ'..'ノ'],
    [*'ハ'..'ポ'],
    [*'マ'..'モ'],
    [*'ヤ'..'ヨ'],
    [*'ラ'..'ロ'],
    [*'ワ'..'ン']
  ]
  
  names.sort!                     # 名前の配列を辞書順にソート
  index=[]
  katakana.each do |gyou|         # 行ごとに名前の先頭がその行に含まれるかを判定
    names.each do |name|
      if gyou.include?(name[0])   # 含まれていた場合は、indexに追加
        if index.empty? || index[-1][0]!=gyou[0]    # 見出しが既に作られているかどうかを判定
          index << [gyou[0],[]]
        end
          index[-1][1]<<name
      end
    end
  end
  return index
end

names = ['キシモト', 'イトウ', 'ババ', 'カネダ', 'ワダ', 'ハマダ','ゴンダ','ドウモト','ヴィクトル']
p name_index names
# => [["ア", ["イトウ", "ヴィクトル"]], ["カ", ["カネダ", "キシモト", "ゴンダ"]], ["タ", ["ドウモト"]], ["ハ", ["ハマダ", "ババ"]], ["ワ", ["ワダ"]]]
```

最初に`('ア'..'オ').to_a`のように、行ごとの配列を作成し、受け取った名前の先頭文字がどの配列に含まれるかをチェックしました。受け取った名前の配列をあらかじめソートしておくことによって、辞書順にindexに追加しています。

# おわりに
Ruby初心者向けのプログラミング問題10問のうち、前半の5問を解いてみました。リファクタリングの余地はかなりあると思うので、コメントをいただければ幸いです。
後半も解き終わり次第、投稿しようと思います。
もっとコードを書いて、スマートに書けるように精進します。