Ruby　カラオケマシン問題　解いてみた（解答例あり）

# はじめに
『プロを目指す人のためのRuby入門』通称チェリー本を学習後のプログラミング初心者です。
インプットしたものを手を動かして実践してみたいなと思ったら、作者の記事を見つけました。
[「アウトプットのネタに困ったらこれ！？Ruby初心者向けのプログラミング問題を集めてみた（全10問）」] (https://blog.jnito.com/entry/2019/05/03/121235)

この二つ目の問題を解いてみました。

[一問目](https://qiita.com/u_nayama/items/389b229bd569fdfc1874)：カレンダー作成問題（たのしいRuby　練習問題）
[二問目](https://qiita.com/u_nayama/items/6009dcfe148e15ad9e1b)：カラオケマシン作成問題
[三問目](https://qiita.com/u_nayama/items/7be7e6c5661d93272de8)：ビンゴカード作成問題
[四問目](https://qiita.com/u_nayama/items/82edf9500c0cdfc263f7)：ボーナスドリンク問題
[五問目](https://qiita.com/u_nayama/items/53c2a4b58840dbccaaf9)：電話帳作成問題

# 問題

詳しくは[実際の問題文](https://blog.jnito.com/entry/2014/06/06/104420)をみてください。
問題知ってる人は、[解答例へジャンプ](#解答例)。
><前提> 

カラオケにはキーを変える機能があります。
+1するとキーが1つ上がります。
-1するとキーが1つ下がります。
たとえば「ドレミファソ」というメロディのキーを2つ上げると「レミファ#ソラ」になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/644441/ae2e577b-ca98-edd4-bbea-1c7becf99bb7.png)
「ドレミファソ」のようにカタカナだとプログラムで扱いづらいので、英語の読み方、つまりアルファベットに置き換えましょう。
ド レ ミ ファ ソ → C D E F G
レ ミ ファ# ソ ラ → D E F# G A

><本題>
かえるのうたのメロディは以下のような文字列で表現されます。
これを(+6)や(-11)など指定大きさ分キーを変えます。

>```ruby
>"C D E F |E D C   |E F G A |G F E   |C   C   |C   C   |CCDDEEFF|E D C   "
>```
実行例はこんな感じになります。

>```ruby
melody = "C D E F |E D C   |E F G A |G F E   |C   C   |C   C   |CCDDEEFF|E D C   "
karaoke = KaraokeMachine.new(melody)
karaoke.transpose(2)
# => "D E F# G |F# E D   |F# G A B |A G F#   |D   D   |D   D   |DDEEF#F#GG|F# E D   "
karaoke.transpose(-1)
# => "B C# D# E |D# C# B   |D# E F# G# |F# E D#   |B   B   |B   B   |BBC#C#D#D#EE|D# C# B   "
# 1オクターブ(12音)以上変えることもできる
karaoke.transpose(14)
# => "D E F# G |F# E D   |F# G A B |A G F#   |D   D   |D   D   |DDEEF#F#GG|F# E D   "
```

# 解答例
こんな感じになりました。

```ruby
class KaraokeMachine

  def initialize(melody)
    @melody = melody
  end

  def transpose(amount)
    scales = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
    @melody.split(/(\w#?)/).map{
            |m| /(\w#?)/.match?(m) ?
            scales[(scales.find_index(m) + amount) % scales.length] : m 
           }.join
  end
end
```

当初下記のコードで完成したと思ってたのですが、

```ruby
class KaraokeMachine

  def initialize(melody)
    @melody = melody
  end

  def transpose(amount)
    scales = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
    @melody.split("").map{
                |m| m == " " || m == "|" ?
                 m : scales[(scales.find_index(m) + amount) % scales.length]
               }.join
  end
end
```
これだと下記のようなメロディが来たときに`アルファベット`と`#`が分離してしまうことに気付き正規表現を用いて修正しました。

```ruby
melody = "F# G# A# B |A# G# F#   |A# B C# D# |C# B A#   |F#   F#   |F#   F#   |F#F#G#G#A#A#BB|A# G# F#   "
```

# 解説
自分の備忘録としても書いておきます。

この問題での音階はシャープも入れると「ドド#レレ#ミファファ#ソソ#ララ#シ」の繰り返しなので,

```
  scales = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]`
```
このように一つの配列にしました。

`melody =  `で受け取った文字列を、`音階`と`それ以外`に区切り、配列にします。

```  ruby
melody = "C D E F |E D C   |E F G A |G F E   |C   C   |C   C   |CCDDEEFF|E D C   "
melody.split(/(\w#?)/)
=> ["", "C", " ", "D", " ", "E", " ", "F", " |", "E", " ", "D", " ", "C", "   |", "E", " ", "F", " ", "G", " ", "A", " |", "G", " ", "F", " ", "E", "   |", "C", "   ", "C", "   |", "C", "   ", "C", "   |", "C", "", "C", "", "D", "", "D", "", "E", "", "E", "", "F", "", "F", "|", "E", " ", "D", " ", "C", "   "]
```

配列の各要素を`mapメソッド`のブロック内で条件の真偽値を求めぞれぞれの値を、新しい配列に追加していきます。
  1. `音階`なら指定された分キーを変更する。
  2. `それ以外`ならそのままの要素を追加。
を次のコードで実行していきます。

```ruby
@melody.split(/(\w#?)/).map{
          |m|  /(\w#?)/.match?(m) ?
          scales[(scales.find_index(m) + amount) % scales.length] : m 
         }.join
```

`音階`の`index`と指定されたキーの増減数を足した数を`scales`の`length`で割ると、キーを変更した先の対応する`音階`の`index`が求められます。
ですので、`scales[ ]`の値に代入し`index`を指定すればキー変更をした`音階`がわかります。

最後にこの`音階`と`それ以外`をもう一度`join`して文字列に直せば完成です。

# さいごに
若干強引気味になってしまったところもあるかなと思います。
何か間違いやご意見あれば教えてください。