お題
引数として与えられた文字列中の小書きの片仮名を 【 】
で囲んだ文字列を返すメソッドを書いてください。
小書きの仮名とは「っ」「ァ」などのことです1。
ここでは,片仮名の「ァ」「ィ」「ゥ」「ェ」「ォ」「ッ」「ャ」「ュ」「ョ」「ヮ」のみを対象とします。
例えば
puts kogaki_mark("キャッシュ")
# => "キ【ャ】【ッ】シ【ュ】"
となります。
コード
def kogaki_mark(str)
str.gsub(/[ァ|ィ|ゥ|ェ|ォ|ッ|ャ|ュ|ョ|ヮ]/, '【\0】')
end
問題点
このコードは,引数によっては正しく動作しません:
puts kogaki_mark("A|B") # => "A【|】B"
与えられた文字列中の |
という文字が 【 】
で囲まれてしまいました。
正規表現の書き方が間違っているのです。
文字クラスの [ ]
の中で選択2の |
を使ってしまっています。どうやらやりたいことを二重に表現してしまったようですね。
文字クラスの [ ]
の中では |
はメタ文字ではなくリテラル3なので,
[ァ|ィ|ゥ|ェ|ォ|ッ|ャ|ュ|ョ|ヮ]
という正規表現は「ァ
とか |
とか ィ
とか |
とか ゥ
とか |
とか ェ
とか |
とか ォ
とか |
とか ッ
とか |
とか ャ
とか |
とか ュ
とか |
とか ョ
とか |
とか ヮ
とか」という意味になります。
なお,[ ]
の中に同じ文字が複数回書かれていても 1 回だけ書いたのと同じです。
正規表現の初心者でこのような書き方をしてしまう方は少なくありません。
テストした文字列に |
が含まれていなければ正しい結果を返すので,ミスに気づきにくいのです。
改善
正しい書き方は
def kogaki_mark(str)
str.gsub(/[ァィゥェォッャュョヮ]/, '【\0】')
end
または
def kogaki_mark(str)
str.gsub(/ァ|ィ|ゥ|ェ|ォ|ッ|ャ|ュ|ョ|ヮ/, '【\0】')
end
です。
「いずれかの文字」という検索パターンは,このように,文字クラスでも選択でも書けます。
どちらがいいのでしょうか?
私は前者を勧めます。
一つの理由は,この例を見れば明らかなように,前者のほうが短いからです。
しかし,二つの文字のいずれか,という検索パターンだと
[AB]
よりも
A|B
のほうが 1 字短くなっています。
短さだけを追求するなら後者に軍配があがりますが,文字クラスで書くことにはほかにもいろいろ利点があります。
まず,保守性です。文字の種類を加減するとき,文字クラスで書いておけば当該の文字を追加・削除するだけで済みますが,選択で書くと |
も一緒に追加・削除しなければなりません。
また,[ ]
の中では,さまざまな記号がリテラルと解釈されることもポイントです。例えば,「丸括弧またはピリオド」は選択で書くと
\(|\)|\.
となりますが,文字クラスで書けば
[().]
のようにエスケープせずにすみます。
また,ほかの正規表現と組み合わせる場合には,文字クラスのほうが簡潔に済む場合があります。例えば「第一次産業」「第二次産業」「第三次産業」という文字列を検索したいとき,もちろん
第一次産業|第二次産業|第三次産業
とも書けますが,文字クラス を使って
第[一二三]次産業
と書けます。この [一二三]
の部分を選択に置き換えようとすると,単純に 一|二|三
として
第一|二|三次産業
と書いたのではダメで
第(?:一|二|三)次産業
のようにグルーピングしなければなりません。
((?: )
は ( )
とすれば若干簡素になりますがキャプチャーというオマケもついてきます4)。