nigakyのブログ

雑多なメモです。

Python で文字列をN文字ずつに分割する

Pythonの文字列に対する split は、指定したセパレータで分割することはできますが、指定文字数で分割することはできません(と思います)。そこでPythonで(できればPython的に)どう書いたらよいか考えてみました。

1文字ずつたどりながら処理する

ぱっと思いつくのはこの方法です。
指定文字数に達したら結果用のリストに追加し、そのリストを返します。

def splitStr(str, num):
    s = ''
    j = 0
    l = []
    for i in str:
        s += i
        j += 1
        if j == num:
            l.append(s)
            j = 0
            s = ''
    if s:
        l.append(s)
    return l

zip で Python 的に処理する

上の方法はあまり Python的ではありませんでした。
zipを使う方がPython的かつ高速らしいので、zipを使う方法を考えてみました。

def splitStr2(str, num):
    l = []
    for i in range(num):
        l.append(str[i::num])
    l = ["".join(i) for i in zip(*l)]
    rem = len(str) % num  # zip で捨てられた余り
    if rem:
        l.append(str[-rem:])
    return l

もっとうまく書けそうですが、なんとなくPython的な気がします。

性能比較

せっかくなので性能を比較してみました。
timeit を利用して 10 万回呼び出した時間を計測しています。

$ python splitStr.py
splitStr: 3.60764718056    # シンプルな方法
splitStr2: 2.22185182571   # zip を使った方法

結果は上の通り、zipを使った方法が 2.2 秒とシンプルな方法の 1.5 倍くらい高速でした。
zipはうまく使うと強力ですね。


以下は今回の計測に利用したスクリプト全文です。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys, timeit

def splitStr(str, num):
    s = ''
    j = 0
    l = []
    for i in str:
        s += i
        j += 1
        if j == num:
            l.append(s)
            j = 0
            s = ''
    if s:
        l.append(s)
    return l

def splitStr2(str, num):
    l = []
    for i in range(num):
        l.append(str[i::num])
    l = ["".join(i) for i in zip(*l)]
    rem = len(str) % num  # zip で捨てられた余り
    if rem:
        l.append(str[-rem:])
    return l

if __name__ == '__main__':
    """ "a" 100 文字を 2 文字ずつのリストに分割
    それを 10 万回繰り返して時間を計測"""
    t = timeit.Timer('splitStr("a" * 100, 2)', "from __main__ import splitStr")
    print "splitStr: " + str(t.timeit(number=100000))
    t = timeit.Timer('splitStr2("a" * 100, 2)', "from __main__ import splitStr2")
    print "splitStr2: " + str(t.timeit(number=100000))