はじめに
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行ずつのコードの役割を意識できていないように感じました。
今後はリファクタリングする時間を多く取って、より良い書き方ができないか、同じような処理はきちんと統一されているか、というところを意識して実装しようと思います。