summer_tree_home

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

Speech Module (Check iO > Home) の続き

他の人の答え

[100の桁、10の桁、1の桁] というようにリストを作っておいて、最後に ' '.join(lst) としている人も多かった。この「文字列.join(リスト) 」スタイルもPythonの使い始めは違和感があったなぁ。

いくつか見ていると、まったく意味不明なものがあった。

def checkio(number):
    spe = dict(enumerate(('', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen')))
    decades = dict(enumerate(('', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety')))
    return (lambda r: r if r else 'zero')(' '.join(filter(lambda p: p, [(spe.get(d.get(2)), 'hundred'*bool(d.get(2)), decades.get(d.get(1)), spe.get(d.get(0)+10*(10<=int(str(number)[-2:])<20))) for d in (dict(enumerate(map(int, str(number)[::-1]))),)][0])))

http://www.checkio.org/mission/speechmodule/publications/JeromeJ/python-3/first/
(CheckiOにログイン&解答済みでないと見えない。)

1行で書いてあるのはすごいが、何をやってるのかさっぱりわからない。とりあえず、見やすくするため改行を入れて、一つ一つ分解して考えてみる。

(lambda r: r if r else 'zero')(
  ' '.join(
    filter(
      lambda p: p, [
        (
          spe.get(d.get(2)),
          'hundred' * bool(d.get(2)),
          decades.get(d.get(1)),
          spe.get(d.get(0) + 10 * (10 <= int(str(number)[-2:]) < 20))
        )
        for d in (
          dict(
            enumerate(
              map(int, str(number)[::-1])
            )
          ), )
      ][0])))

最後の方にある、 str(number)[::-1] というのは、与えられた数字を反転させている。

5 → '5'
20 → '02'
123 → '321'

dict(enumerate(map(int, str(number)[::-1])))
で、反転した数字を1文字ずつ分解して、indexをつけてdictに変換している。

5 → {0: 5}
20 → {0: 0, 1: 2}
123 → {0: 3, 1: 2, 2: 1}

このdictを [ (タプル) for d in (dict(~),) ][0] としている。何をやってるのかわかりにくいが、

  1. d = dict(~)
  2. dを使ってタプルを作成

を1文で書いているだけだ。

タプルの作成部分を見てみよう。

(
    spe.get(d.get(2)),
    'hundred' * bool(d.get(2)),
    decades.get(d.get(1)),
    spe.get(d.get(0) + 10 * (10 <= int(str(number)[-2:]) < 20))
)

まず、d.get(2)は、dictのkey=2、つまり100の桁を取得している。100の桁がなければNoneだ。
次に、str(number)[-2:] だが、 lst[-2:] は後ろから2番目から最後までを返すわけだから、数字の100の桁を削除しているわけだ。ところで、Pythonって '5'[-2:] でも範囲外エラーにならないんだな。

5 → '5'
20 → '20'
123 → '23'

あとは、文字列*bool や int*bool が出てくるが、こういう場合は True=1, False=0 となるようだ。

整理すると、タプルの4要素は、次のようになる。

spe.get(d.get(2)) 100の桁、'one'~'nine' or None
'hundred' * bool(d.get(2)) 'hundred' or ''
decades.get(d.get(1)) 10の桁、'twenty'~'ninety' or ''
spe.get(d.get(0) + ... 1から19まで、'one'~'nineteen' or ''

5 → (None, '', None, 'five')
20 → (None, '', 'twenty', '')
123 → ('one', 'hundred', 'twenty', 'three')

さて、こうして作ったタプルをfilterにかけて、タプルの要素から、Noneや''を削除する。

filter(lambda p: p, tuple)

5 → ('five')
20 → ('twenty')
123 → ('one', 'hundred', 'twenty', 'three')

あとは、' '.join(~) でスペースでつなげて文字列にする。
一番最初の、 (lambda r: r if r else 'zero')(~) 部分だが、これは

func = lambda r: r if r else 'zero'  # 関数をラムダ式で定義
func(~)  # 関数を呼び出し

を1行で書いているだけ。このラムダ式は、number=0のときに'zero'を返すためにある。(要件は、0 < N < 1000だから、別になくてもよい。)


というわけで、意味不明な1行だが、なんとか解読できた。
自分ではこういう書き方はしないけど、Pythonの勉強にはなった。