CodeIQ:作詞支援ツールを作ろう

私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「作詞支援ツールを作ろう」(CodeIQ)を参照してください。

問題の概要

問題を引用します。
あなたが所属しているプロジェクトでは、様々な楽曲を扱っていましたが、ある日作詞家から作詞支援ツールの開発を依頼されました。
それは、特定の言葉と同じ韻を踏む(母音が一致している)言葉を探すというもので、例えば「試合(SIAI)」と「気合(KIAI)」というように、 特にヒップホップでは欠かせない要素です。
もちろん、実際の曲作りはこんな定石に従うとは限りませんが、まずは「隗より始めよ」ということで、手始めとして入力された文から母音を抽出するプログラムを作ることになりました。

求められるプログラムの前提条件は、以下の通りとなります。
標準入力から、ひらがなのみで構成された文が一行送られる
文字コードとしてUTF-8を想定する
入力文は、1文字以上32文字以下とし、後述する表に定める文字コードのひらがな以外は含まない
入力文から促音である「っ」を取り除いたうえで、ローマ字(Wikipediaへのリンク)に変換し、母音のみを抽出する
母音のみの抽出となるため、ローマ字の種類は訓令式かヘボン式か問わないものとする
母音のみの抽出となるため、撥音「ん」は無視して構わない
”てぃ”など、ローマ字の種類によって未定義の文字は入力に含まれないものとする
母音は小文字のアルファベット(a/i/u/e/o)として扱うこと
抽出した母音を連結し、1つの文字列として標準出力に返すこと

文字コード表(UTF-8)は省略

そして以下が、入力と出力例になります。

No.入力例出力例
1さとうaou
2しょうゆouu
3にほんしゅiou

作詞支援というのは奥の深いテーマで、今回取り上げた語呂以外にも、”意味の近い単語を探したい”など実際のニーズは様々です。
ただ一方で、既存サービスはいくつかありながらも、決定版といえるようなものがまだ世の中に存在しないのも事実です。
特に、個人的には”新語の対応”が課題と感じています…

この問題をきっかけに、世の中にはこんなニーズがあるんだということを知ってもらい、挑戦者の皆様が今後何か新しいサービスを開発する上でのヒントになれば幸いです。

【問題】
標準入力から、ひらがなのみで構成された文が一行送られます。
この文から、促音を取り除いた上でローマ字に変換し、母音のみ(a/i/u/e/o)を抽出して、その結果を連続した文字列として標準出力に返してください。

私のプログラム

Rubyで解答しています。

#!/usr/bin/ruby
#encoding: utf-8

SMALL = ["ぁ", "ぃ", "ぅ", "ぇ", "ぉ", "ゃ", "ゅ", "ょ"]
TABLE = {
	"あ"=>"a", "い"=>"i", "う"=>"u", "え"=>"e", "お"=>"o",
	"ぁ"=>"a", "ぃ"=>"i", "ぅ"=>"u", "ぇ"=>"e", "ぉ"=>"o",
	"か"=>"a", "き"=>"i", "く"=>"u", "け"=>"e", "こ"=>"o",
	"が"=>"a", "ぎ"=>"i", "ぐ"=>"u", "げ"=>"e", "ご"=>"o",
	"さ"=>"a", "し"=>"i", "す"=>"u", "せ"=>"e", "そ"=>"o",
	"ざ"=>"a", "じ"=>"i", "ず"=>"u", "ぜ"=>"e", "ぞ"=>"o",
	"た"=>"a", "ち"=>"i", "つ"=>"u", "て"=>"e", "と"=>"o",
	"だ"=>"a", "ぢ"=>"i", "づ"=>"u", "で"=>"e", "ど"=>"o",
	"な"=>"a", "に"=>"i", "ぬ"=>"u", "ね"=>"e", "の"=>"o",
	"は"=>"a", "ひ"=>"i", "ふ"=>"u", "へ"=>"e", "ほ"=>"o",
	"ぱ"=>"a", "ぴ"=>"i", "ぷ"=>"u", "ぺ"=>"e", "ぽ"=>"o",
	"ば"=>"a", "び"=>"i", "ぶ"=>"u", "べ"=>"e", "ぼ"=>"o",
	"ま"=>"a", "み"=>"i", "む"=>"u", "め"=>"e", "も"=>"o",
	"や"=>"a",            "ゆ"=>"u",            "よ"=>"o",
	"ゃ"=>"a",            "ゅ"=>"u",            "ょ"=>"o",
	"ら"=>"a", "り"=>"i", "る"=>"u", "れ"=>"e", "ろ"=>"o",
	"わ"=>"a",                                 "を"=>"o",
	"ゎ"=>"a",
}

def solve(line)
	ret = []

	line.chars{|c|
		ret.pop if SMALL.include?(c)
		ret << TABLE[c] if TABLE[c] != nil
	}

	return ret.join("")
end

# main
while line = gets
	line.strip!
	next if line.empty?

	puts solve(line)
end

解説

特に難しくはないと思います。
多少厄介なのは「っ」と「しゃ」の様なときの扱いでしょうか?

考え方

面倒臭いですがひらがなと対応する母音の対応表を作ります。
あとは入力値1文字ずつをその標に従って対応する母音にするだけです。

「しゃ」の様な場合、「ia」ではなく「a」にするのは小さい文字が出たら一つ前の結果を削除してから小さい文字の母音を追加することで対応します。
「っ」は対応する母音なしとすれば無視できます。

main

SMALLは「っ」を覗く小さい文字のリストで小さい文字のチェックに使います。
TABLEはひらがなと母音の対応表です。「っ」と「ん」は対応する母音なしとして表に含めません。
入力値をsolve()に渡し、結果を印字します。

solve(n)

引数を1文字ずつ母音に直します。
結果はretに1つずつ追加されます。
30行目で小さい文字をチェックし、小さい文字だったらretをpop()することで直前の結果を無視します。これで「しゃ」の様な場合に対応できます。
31行目で表にあるひらがななら結果に追加し、なければ無視します。

retをjoin()した文字列を返却します。

雑感

経験上、この出題者の問題は仕様漏れなどのミスが多いです。
この問題にもあって、「を」が文字コード表から漏れていて、やっぱりなと思ったのを覚えています。