こういう処理がしたいときがある。
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桁ずつでも良いかという気もしなくもなく・・・。