summer_tree_home

Check iOでPython3をマスターするぜっ

Speech Module (Check iO > Home)

どんな問題?

長々とストーリーが書いてるけど、英語苦手なのでスルー。ようするに、数字を英語に変換するだけの問題だ。

数字は 0 < N < 1000 の整数のみ。単語と単語の間にはスペース1個が必要。

例はこちら

checkio(4)=='four'
checkio(143)=='one hundred forty three'
checkio(12)=='twelve'
checkio(101)=='one hundred one'
checkio(212)=='two hundred twelve'
checkio(40)=='forty'

どうやって解く?

まずは英語での数え方を思い出さないと・・・。

1~12 は、one two three ... twelve と固有の呼び方。
13~19 は、○○teen。
20 は、twenty
21~29は、twenty + one ~ nine
30~99 は、同じような感じで続く。
100 は one hundred

あれ、200って、two hundredだっけ?two hundredsだっけ?
ググってみた。two hundred でいいみたい。「幾百人」とかは、"hundreds of people"のように複数形になると、、、そういえば習ったっけ。というか、上の例に載ってたわ。

あれ、上の例で、143 が one hundred forty three となってるけど、 one hundred and forty three じゃないのか?
ググってみた。米口語ではandを略すこともある、だってさ。知らんわ。
英語嫌いとしては、だんだんイライラしてきたよ。


解法は簡単。まず、100未満と100以上でわける。
100未満については、1~20は辞書を定義しておく。21~99は、'twenty'~'ninety' + 'one'~'nine' で作成できる。
100以上の数字は、まず100の桁を 'one'~'nine' + 'hundred' で作成できるので、100未満の部分を足せば良い。

では、Solve it!ボタンをクリック!

あれ、すでに、あらかじめ必要な単語が定義されているじゃないか。

FIRST_TEN = ["one", "two", "three", "four", "five", "six", "seven",
             "eight", "nine"]
SECOND_TEN = ["ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
              "sixteen", "seventeen", "eighteen", "nineteen"]
OTHER_TENS = ["twenty", "thirty", "forty", "fifty", "sixty", "seventy",
              "eighty", "ninety"]
HUNDRED = "hundred"

じゃあ、あとは処理を書くだけ。

def checkio(number):
    def under100(n):
        if n <= 9:
            return FIRST_TEN[n - 1]
        elif n <= 19:
            return SECOND_TEN[n - 10]
        else:
            s = OTHER_TENS[n // 10 - 2]
            if n % 10 > 0:
                s += ' ' + FIRST_TEN[n % 10 - 1]
            return s
    if number < 100:
        return under100(number)
    else:
        return FIRST_TEN[number // 100 - 1] + ' ' + HUNDRED + ' ' + under100(number % 100)

100未満の処理を under100()にまとめて、100未満と100以上で分岐。さくっと完成、Run&Check!

FAIL

Your result: "one hundred nine"
Right result: "one hundred"
Fail: checkio(100)

えっ?
あー、そうか、numberが100のときは、under100に0が渡されるけど、引数が0のときを想定してなかった。FIRST_TEN[-1]で'nine'が返ってしまったらしい。
Pythonのリストでは lst[-n] で後ろからn番目の要素を返すため、範囲外エラーにはならない。この機能って便利なんだけど、他の言語(C#)から来た人は、エラーの出ないバグに悩まされる原因にもなる。

もう一つ、CheckiOって、あらかじめ__main__節に書いてあるものだけテストしてると思ってた。checkio(100) は書いてないけど、ちゃんとテストしてるんだね。

if __name__ == '__main__':
    assert checkio(4) == 'four', "1st example"
    assert checkio(133) == 'one hundred thirty three', "2nd example"
    assert checkio(12) == 'twelve', "3rd example"
    assert checkio(101) == 'one hundred one', "4th example"
    assert checkio(212) == 'two hundred twelve', "5th example"
    assert checkio(40) == 'forty', "6th example"

というわけで、初めてのFAILである。バッジはもらえないのかな?

numberが100以上の場合は、 number % 100 > 0 のときだけ、under100()を呼び出して追加するように修正した。
ついでに、under100() にnの範囲チェック(assert)を入れておいた。

    def under100(n):
        assert 1 <= n <= 99
        if n <= 9:
            return FIRST_TEN[n - 1]
        elif n <= 19:
            return SECOND_TEN[n - 10]
        else:
            s = OTHER_TENS[n // 10 - 2]
            if n % 10 > 0:
                s += ' ' + FIRST_TEN[n % 10 - 1]
            return s
    if number < 100:
        return under100(number)
    else:
        s = FIRST_TEN[number // 100 - 1] + ' ' + HUNDRED
        if number % 100 > 0:
            s += ' ' + under100(number % 100)
        return s

これでどやっ!

Task solved!

この方法だと、最後から2行目を

            s += ' and ' + under100(number % 100)

に書き換えるだけで、one hundred and forty three 形式にできる。
クライアントが急に「普通、hundred の後に and 入れるよね?だよね?」なんて言い出しても安心だ。