はじめに
Ruby初心者向けのプログラミング問題を集めてみたから、今回は電話帳作成問題を解いてみました。
自分が書いたソースコードと、先輩にいただいた回答例を紹介していきます。
以下のINのような名前の配列を渡すと、OUTのように50音の音別にグループ分けされた配列が返るようにします。
- IN:
['キシモト', 'イトウ', 'ババ', 'カネダ', 'ワダ', 'ハマダ'] - OUT:
[ ['ア', ['イトウ']], ['カ', ['カネダ', 'キシモト']], ['ハ', ['ハマダ', 'ババ']], ['ワ', ['ワダ']] ]
要求仕様
- カタカナ文字列の配列を渡すと、ア段の音別にグループ分けした配列を返すプログラムを作成すること
- 各要素は 50 音順にソートもすること
自分が書いたソースコード
class NameIndex
KANA_GROUPS = [
('ア'..'オ').to_a,
('カ'..'ゴ').to_a,
('サ'..'ゾ').to_a,
('タ'..'ド').to_a,
('ナ'..'ノ').to_a,
('ハ'..'ボ').to_a,
('マ'..'モ').to_a,
('ヤ'..'ヨ').to_a,
('ラ'..'ロ').to_a,
('ワ'..'ン').to_a
].freeze
def self.create_index(names)
KANA_GROUPS.map do |kana_group|
target_names = names.sort.select do |name|
kana_group.include?(name[0])
end
[kana_group.first, target_names] unless target_names.empty?
end.compact
end
end
処理について
まず、定数KANA_GROUPSで、50音を行ごとに分けた配列を定義しています。
create_indexメソッドでは、KANA_GROUPSをレシーバにmapを使用し、
ループの中では、引数のnamesをsortで並び替えた後、selectで特定の名前を取得しています。
kana_groupの先頭の文字と、取得した名前たち(target_names)をセットにした配列を作成しています。
また、結果にnilが含まれてしまうので、最後にcompactしています。
改善点
-
KANA_GROUPSに、ヴとポが含まれていない。 -
mapの中で、namesに対してsortしているので無駄に処理が走ってしまう。- ループより前で、
sortしたものを変数に入れておくと良かった。
- ループより前で、
- 最後に
compactしているので、無駄に処理が走ってしまう。-
map+compactで書けるときは、injectを使うようにする。 -
injectが使えるときは、each_with_objectを使うようにする。
-
-
name[0]とkana_group.first、記法が統一されていない。 -
selectの部分は、別メソッドに切り出したほうが可読性が上がる。- どのような処理をしているのかがメソッド名から想像できるため、分かりやすくなる。
回答例
class NameIndex
SYLLABLES = [
[*('ア'..'オ'), 'ヴ'],
('カ'..'ゴ').to_a,
('サ'..'ゾ').to_a,
('タ'..'ド').to_a,
('ナ'..'ノ').to_a,
('ハ'..'ポ').to_a,
('マ'..'モ').to_a,
('ヤ'..'ヨ').to_a,
('ラ'..'ロ').to_a,
('ワ'..'ン').to_a
].freeze
class << self
def create_index(names)
names.sort.group_by(&method(:initial_syllable)).to_a
end
private
def initial_syllable(name)
SYLLABLES.find { |column| column.include?(name.chr) }.first
end
end
end
処理について
まず、定数SYLLABLESで、50音を行ごとに分けた配列が定義されています。
ア行は、配列展開が使用されており、ヴも含まれています。
create_indexでは、引数のnamesをsortした後、group_byでグループ分けしたハッシュを配列にしています。
(group_byは、ブロックの返り値をキー、キーに対応する要素の配列を値とするハッシュを返します。)
&method(:initial_syllable)で、要素1つずつに対して、initial_syllableを呼んでいます。
initial_syllableメソッドでは、引数のnameが属するグループの段を返すために、SYLLABLESからnameの1文字目に当てはまる行の先頭を取得しています。
学んだこと
- 範囲オブジェクトを使うときは、必要な値が全て含まれているか確認する。
- グルーピングしたいときは、
group_byが使えないか検討する。 - ループを回すときは、条件式が少なく書ける方をレシーバにする。
- 1つのメソッドで複数の処理をしすぎないようにする。(別メソッドに切り分ける。)
まとめ
今回の電話帳問題を解いてみて、コードの統一感や、1行ずつのコードの役割を意識できていないように感じました。
今後はリファクタリングする時間を多く取って、より良い書き方ができないか、同じような処理はきちんと統一されているか、というところを意識して実装しようと思います。