はじめに
『プロを目指す人のためのRuby入門』通称チェリー本を学習後のプログラミング初心者です。
インプットしたものを手を動かして実践してみたいなと思ったら、作者の記事を見つけました。
[「アウトプットのネタに困ったらこれ!?Ruby初心者向けのプログラミング問題を集めてみた(全10問)」] (https://blog.jnito.com/entry/2019/05/03/121235)
この二つ目の問題を解いてみました。
一問目:カレンダー作成問題(たのしいRuby 練習問題)
二問目:カラオケマシン作成問題
三問目:ビンゴカード作成問題
四問目:ボーナスドリンク問題
五問目:電話帳作成問題
問題
詳しくは実際の問題文をみてください。
問題知ってる人は、解答例へジャンプ。
<前提>
カラオケにはキーを変える機能があります。
+1するとキーが1つ上がります。
-1するとキーが1つ下がります。
たとえば「ドレミファソ」というメロディのキーを2つ上げると「レミファ#ソラ」になります。
「ドレミファソ」のようにカタカナだとプログラムで扱いづらいので、英語の読み方、つまりアルファベットに置き換えましょう。
ド レ ミ ファ ソ → C D E F G
レ ミ ファ# ソ ラ → D E F# G A
<本題>
かえるのうたのメロディは以下のような文字列で表現されます。
これを(+6)や(-11)など指定大きさ分キーを変えます。
"C D E F |E D C |E F G A |G F E |C C |C C |CCDDEEFF|E D C "
実行例はこんな感じになります。
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
当初下記のコードで完成したと思ってたのですが、
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
これだと下記のようなメロディが来たときにアルファベット
と#
が分離してしまうことに気付き正規表現を用いて修正しました。
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 =
で受け取った文字列を、音階
とそれ以外
に区切り、配列にします。
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メソッド
のブロック内で条件の真偽値を求めぞれぞれの値を、新しい配列に追加していきます。
-
音階
なら指定された分キーを変更する。 -
それ以外
ならそのままの要素を追加。
を次のコードで実行していきます。
@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
して文字列に直せば完成です。
さいごに
若干強引気味になってしまったところもあるかなと思います。
何か間違いやご意見あれば教えてください。