Striped Words (O'Reilly) - ストライプワード
どんな問題?
Striped Words
http://www.checkio.org/mission/striped-words/
テキスト中に、ストライプワード(母音と子音が交互に並んでいる単語)が何個でてくるか数えよ。
単語は、スペースと句読点で区切られている。
数字や、数字と文字の組み合わせは、単語ではないものとする。
一文字の単語は、ストライプワードとしてカウントしないこと。
引数は、テキスト(文字列)。
戻り値は、ストライプワードの数。
例題:
checkio("My name is ...") == 3 checkio("Hello world") == 0 checkio("A quantity of striped words.") == 1, "Only of" checkio("Dog,cat,mouse,bird.Human.") == 3
どうやって解く?
- テキストをスペースと句読点で分割する。
- 数字や数字を含む単語を除外する。
- ストライプワードかどうかをチェックする。
という流れでいけそうだ。
句読点の定義は?
そもそも句読点(punctuation)ってどこまで含まれるのだろう?例題に出てくるのは「.,」だが、「:;?!」なども句読点になるのだろうか?
punctuationで検索してみたら、Pythonでは string.punctuation として定義されているようだ。また、string.whitespace というのもあったので、この2つを使うことにする。
1.テキストをスペースと句読点で分割する。
str.split() では区切り文字を一種類しか指定できないので、複数の区切り文字を指定して文字列を分割するのは少しやっかい。
区切り文字を1種類にまとめてからsplitする方法がある。例えば、区切り文字が「 」「.」「,」の3種類なら、「 」と「.」を「,」に置換してから、「,」でsplitする。
s.replace(' ', ',').replace('.', ',').split(',')
しかし、区切り文字が増えてくるとちょっと面倒だ。今回は、正規表現を使うことにした。
正規表現はよく知らないので、ググってみると、re.split() でいいみたいだ。
def split_with_separators1(s, separators): pattern = '|'.join(re.escape(s) for s in separators) return re.split(pattern, s)
ただ、これだと、text = "My name is ..." のときに
['My', 'name', 'is', '', '', '', '']
となってしまう。
''を含まないようにするなら、re.findall()を使う方法もある。
def split_with_separators2(s, separators): return re.findall("[^" + re.escape(separators) + "]+", s)
「区切り文字以外から成る一文字以上の文字列をすべて取得」という意味になる。これだと、結果は
['My', 'name', 'is']
となる。今回はこっちを使おうかな。
(追記)
この関数では、1文字の区切り文字を複数指定することになる。複数文字の区切り文字を複数指定することはできない。
2.数字や数字を含む単語を除外する。
普通に一文字ずつ調べてみる。
def contains_digits1(s): return any(c in string.digits for c in word)
正規表現を使う方法も調べてみた。
def contains_digits2(s): return re.search('\d', s) is not None
3.ストライプワードかどうかをチェックする。
単語の奇数番目の文字と、偶数番目の文字をチェックする。「偶数番目がすべて母音 かつ 奇数番目がすべて子音」または「偶数番目がすべて子音 かつ 奇数番目がすべて母音」であればいい。
VOWELS = set("AEIOUY") CONSONANTS = set("BCDFGHJKLMNPQRSTVWXZ") def is_striped_word(s): even, odd = set(s[::2].upper()), set(s[1::2].upper()) return (even <= VOWELS and odd <= CONSONANTS) or (odd <= VOWELS and even <= CONSONANTS)
ただ、これだと、sが一文字のときは、evenまたはoddが{}となって、戻り値がTrueになってしまうので、
def is_striped_word(s): even, odd = set(s[::2].upper()), set(s[1::2].upper()) if len(even) == 0 or len(odd) == 0: return False return (even <= VOWELS and odd <= CONSONANTS) or (odd <= VOWELS and even <= CONSONANTS)
としておいた。
(注)公開後に気付いたが、単に
if len(s) <= 1: return False
で良かった。orz
まとめ
import string import re VOWELS = set("AEIOUY") CONSONANTS = set("BCDFGHJKLMNPQRSTVWXZ") def split_with_separators(s, separators): return re.findall("[^" + re.escape(separators) + "]+", s) def contains_digits(s): return re.search('\d', s) is not None def is_striped_word(s): even, odd = set(s[::2].upper()), set(s[1::2].upper()) if len(even) == 0 or len(odd) == 0: return False return (even <= VOWELS and odd <= CONSONANTS) or (odd <= VOWELS and even <= CONSONANTS) def checkio(text): words = split_with_separators(text, string.whitespace + string.punctuation) return len([w for w in words if not contains_digits(w) and is_striped_word(w)])
http://www.checkio.org/mission/striped-words/publications/natsuki/python-3/first/
見直すと、あれこれ修正したい部分が出てくるけど、、、まあいいか。
ところで、「複数の区切り文字を指定して文字列を分割できるメソッド」は、Python標準で対応してほしいなぁ。