静かなる名辞

pythonとプログラミングのこと


【python】数字を1桁ずつに分解

 こういう処理がしたいときがある。

i = 2049
lst = []
while i > 0:
    lst.append(i%10)
    i //= 10 # 必須
lst.reverse()
print(lst)
# 結果-> [2, 0, 4, 9]

 「もうできたじゃん」という意見もあると思うが、C言語のコードみたいでいかにもpythonicじゃない。

 これはこれで良いとして、もっと良い方法がないか考えてみる。

先行研究

 同じことを考えた人はたくさんいるようだ。日本語でググって出てきたページを幾つか貼る。
python で 数値を桁別に取得するには - スタック・オーバーフロー
数値を桁ごとにリストに格納する方法,またその逆(Python) - done is better than perfect
Python - python リスト型 変数|teratail

 方法としては、上の方法の変形か、strに変換して1桁ずつ読むかのどちらかという感じ。

 strに変換するというのは次のようなコード。

[int(i) for c in str(i)]

 
 記述量は少ないが、いかんせん無駄に型変換が走るのはスマートではない。

別の方法1

 再帰で書いてみた。

def digit(i, lst=[]):
    if i > 0:
        lst.append(i%10)
        return digit(i//10, lst)
    else:
        lst.reverse()
        return lst
print(digit(2049))
# 結果-> [2, 0, 4, 9]

 lispだったら綺麗な奴。pythonだと微妙かも。lstを使い回すのが嫌なら、下の書き方もある。

def digit(i):
    if i > 0:
        return digit(i//10) + [i%10]
    else:
        return []

 どうせ末尾再帰最適化なんてされないので、listオブジェクト生成のオーバーヘッド以外にデメリットはない(そのオーバーヘッドがでかかったりするが)。appendを使えばそれすら消せそう。

 そして、これができるということは、関数定義だけならワンライナーにもできる。

digit = lambda i:(digit(i//10) + [i%10]) if i > 0 else []

 なぜこんなことをするのか? 私の趣味です。

別の方法2

 ジェネレータを使って書くこともできると思う。やってない。

別の方法3

 思いつかなかった・・・。

まとめ

 普通にC言語っぽく書けば良い気がしてきたなぁ・・・。

追記

 実行速度を測っていなかったので、計測してみました。

import timeit

def f1(i):
    lst = []
    while i > 0:
        lst.append(i%10)
        i //= 10 # 必須
    lst.reverse()
    return lst

def f2(i):
    return [int(x) for x in str(i)]

print(timeit.timeit(lambda :f1(1)))
print(timeit.timeit(lambda :f2(1)))
""" =>
0.47740281200094614
1.1206639419979183
"""

print(timeit.timeit(lambda :f1(1234567890)))
print(timeit.timeit(lambda :f2(1234567890)))
""" =>
2.2151244249980664
3.7194496169977356
"""

print(timeit.timeit(lambda :f1(int("1234567890"*10))))
print(timeit.timeit(lambda :f2(int("1234567890"*10))))
""" =>
28.711613783001667
25.051117279996106
"""

 桁数が小さければ割り算で処理する方法が速いが、大きくなるとどこかで逆転する。違いは倍程度には収まるという結果になりました。

 まあ、そしたらstrに変換して1桁ずつでも良いかという気もしなくもなく・・・。