Adding Thousand Separators using itertools

この問題が面白そうだなと思い、私もPythonで挑戦してみました。目的は、数字にコンマを振ることです。

Python のジェネレータ (2)」に引き続き、ジェネレータに慣れるための練習。o(+_+)o どかにまた例題はないかと散策していたら、「数字にコンマを振る」が良さげ。

Python のジェネレータ (3) - 数字にコンマを振る | すぐに忘れる脳みそのためのメモ

当初は、先日Schemeでコーディングしたこの問題と同様、継続渡し形式でコードを書く良い問題であるのではないかと思っていました。しかし、これが大変難しい。そのため、イテレータ、itertoolsモジュールを積極的に使う方針としてみました。出来上がったコードは次のようなものです。Python3.0を使っています。

from itertools import chain, cycle, islice

def thousand_separated(n):
    return ''.join(chain(*zip(reversed(str(n)),
                              cycle(('', '', ',')))))[::-1].lstrip(',')

うーん、いいのか悪いのか。最後にstr.lstrip()で整形しているのが悲しいところ。


テストコードを含めたコードとその結果を以下に。数値の生成にもitertoolsで。

thousand_separated.py:

#!/usr/local/bin/python3.0 -t

from itertools import chain, cycle, islice

def thousand_separated(n):
    return ''.join(chain(*zip(reversed(str(n)),
                              cycle(('', '', ',')))))[::-1].lstrip(',')

if __name__ == '__main__':
    for i in range(20):
        n = int(''.join(islice(cycle('1234567890'),
                               i+1)))
        print('%27s' % thousand_separated(n))
> ./thousand_separated.py
                          1
                         12
                        123
                      1,234
                     12,345
                    123,456
                  1,234,567
                 12,345,678
                123,456,789
              1,234,567,890
             12,345,678,901
            123,456,789,012
          1,234,567,890,123
         12,345,678,901,234
        123,456,789,012,345
      1,234,567,890,123,456
     12,345,678,901,234,567
    123,456,789,012,345,678
  1,234,567,890,123,456,789
 12,345,678,901,234,567,890


さて、関数内で何が起こっているかをPostScriptで可視してみました。目的は数値にコンマを挿入することでした。

これではよく分かりませんね。コンマの行にも罫線を追加してやります。

空のセルを明示するために空文字列('')を入れてみます。また、コンマも文字列とします(',')。何か見えてきましたね。

コンマは最後の桁から3桁ずつ数えるのでした。左右反転してみましょう。

同じ列のセルは上から読むと決め、上下の行のズレを補正してみましょう。2行の行列のようになりますね。

なるほどこの2つの行をイテレータとして作成してやり、合成すれば良さそうです。では、str(n)で数値を文字列に変換します。

次に、reversed()を用いて文字列を反転します。ここからはイテレータの世界となります。これで数値側のイテレータの準備は完了です。

さて、今度はカンマ側のイテレータを見てみます。まず、('', '', ',')としてタプルを作成します。タプルもイテレータとして扱うことが出来ます。

これを、itertools.cycle()を用いて無限に反復させます。

反転させた数値のイテレータとカンマ側のイテレータを合成します。合成にはzip()を用います。

カンマ側のイテレータは無限に要素を返しますが、zip()は与えられたイテレータの一つが終了した場合、反復を終了します。言い換えれば、最も短いイテレータに合わせ、切りそろえてくれるような働きをします。

zip()の結果は行列のように見えますよね。実際、これは先に挙げた行列を転置した行列となっています。

さらにitertools.chain()を用いて、一次元のイテレータに変換します。

このイテレータを''.join()すると、空の文字が押しつぶされたような状態になります。カンマの引用符も消してしまいましょう。

最後にスライスを用いて文字列を反転させます。

入力した数値の桁数が3で割り切れる場合、先頭にコンマがついてします。そのため、str.lstrip()で削除しています。

まとめ

イテレータ、itertoolsモジュールは激しく便利ですね。またこのような問題を見つけたらエントリにしたいなと思っています。