string.split()
リストインスタンスを返します。代わりにジェネレータを返すバージョンはありますか?ジェネレータバージョンを使用することに何らかの理由がありますか?
split
の1つは文字列であり、の結果を処理するジェネレータを返しましたsplit
。そもそもsplit
、ジェネレーターを元に戻す方法があるかどうかと思いました。
string.split()
リストインスタンスを返します。代わりにジェネレータを返すバージョンはありますか?ジェネレータバージョンを使用することに何らかの理由がありますか?
split
の1つは文字列であり、の結果を処理するジェネレータを返しましたsplit
。そもそもsplit
、ジェネレーターを元に戻す方法があるかどうかと思いました。
回答:
re.finditer
メモリのオーバーヘッドをかなり最小限に抑える可能性が非常に高いです。
def split_iter(string):
return (x.group(0) for x in re.finditer(r"[A-Za-z']+", string))
デモ:
>>> list( split_iter("A programmer's RegEx test.") )
['A', "programmer's", 'RegEx', 'test']
編集:私のテスト方法論が正しいと仮定して、これはpython 3.2.1で一定のメモリを必要とすることを確認しました。私は非常に大きなサイズ(1 GB程度)の文字列を作成し、for
ループで反復可能オブジェクトを反復処理しました(リスト内包表記ではなく、余分なメモリが生成されていました)。これにより、メモリが著しく増加することはありませんでした(つまり、メモリが増加した場合、1GBの文字列よりもはるかに少なくなります)。
a_string.split("delimiter")
か?
str.split()
正規表現を受け入れない、それはre.split()
あなたが考えていることです...
メソッドのoffset
パラメーターを使用して記述する最も効率的なstr.find()
方法です。これにより、大量のメモリ使用が回避され、不要な場合は正規表現のオーバーヘッドに依存します。
[編集2016-8-2:これを更新して、オプションで正規表現の区切り文字をサポートします]
def isplit(source, sep=None, regex=False):
"""
generator version of str.split()
:param source:
source string (unicode or bytes)
:param sep:
separator to split on.
:param regex:
if True, will treat sep as regular expression.
:returns:
generator yielding elements of string.
"""
if sep is None:
# mimic default python behavior
source = source.strip()
sep = "\\s+"
if isinstance(source, bytes):
sep = sep.encode("ascii")
regex = True
if regex:
# version using re.finditer()
if not hasattr(sep, "finditer"):
sep = re.compile(sep)
start = 0
for m in sep.finditer(source):
idx = m.start()
assert idx >= start
yield source[start:idx]
start = m.end()
yield source[start:]
else:
# version using str.find(), less overhead than re.finditer()
sepsize = len(sep)
start = 0
while True:
idx = source.find(sep, start)
if idx == -1:
yield source[start:]
return
yield source[start:idx]
start = idx + sepsize
これは好きなように使えます...
>>> print list(isplit("abcb","b"))
['a','c','']
find()またはスライスが実行されるたびに文字列内をシークするコストは少しありますが、文字列はメモリ内の連続した配列として表されるため、これは最小限に抑える必要があります。
これはをsplit()
介しre.search()
て実装されたジェネレーターバージョンであり、あまりにも多くの部分文字列を割り当てる問題はありません。
import re
def itersplit(s, sep=None):
exp = re.compile(r'\s+' if sep is None else re.escape(sep))
pos = 0
while True:
m = exp.search(s, pos)
if not m:
if pos < len(s) or sep is not None:
yield s[pos:]
break
if pos < m.start() or sep is not None:
yield s[pos:m.start()]
pos = m.end()
sample1 = "Good evening, world!"
sample2 = " Good evening, world! "
sample3 = "brackets][all][][over][here"
sample4 = "][brackets][all][][over][here]["
assert list(itersplit(sample1)) == sample1.split()
assert list(itersplit(sample2)) == sample2.split()
assert list(itersplit(sample3, '][')) == sample3.split('][')
assert list(itersplit(sample4, '][')) == sample4.split('][')
編集:区切り文字が指定されていない場合、周囲の空白の処理が修正されました。
re.finditer
?
提案されたさまざまな方法のパフォーマンステストをいくつか行いました(ここでは繰り返しません)。いくつかの結果:
str.split
(デフォルト= 0.3461570239996945re.finditer
(ninjageckoの答え)= 0.698872097000276str.find
(エリコリンズの回答の1つ)= 0.7230395330007013itertools.takewhile
(イグナシオバスケスアブラムスの答え)= 2.023023967998597str.split(..., maxsplit=1)
再帰= N / A††再帰応答(string.split
を使用maxsplit = 1
)は、適切な時間内に完了しないためstring.split
、短い文字列では速度が向上する可能性がありますが、メモリが問題にならない短い文字列の使用例を確認できません。
を使用timeit
してテスト:
the_text = "100 " * 9999 + "100"
def test_function( method ):
def fn( ):
total = 0
for x in method( the_text ):
total += int( x )
return total
return fn
これはstring.split
、メモリ使用量にもかかわらず、なぜそれほど高速であるかについて別の質問を投げかけます。
これが私の実装です。これは、他の答えよりもはるかに高速で完全です。ケースごとに4つのサブ機能があります。
main str_split
関数のdocstringをコピーします。
str_split(s, *delims, empty=None)
s
空の部分を省略して、残りの引数で文字列を分割します(empty
キーワード引数がその原因です)。これはジェネレーター関数です。
区切り文字が1つだけ指定された場合、文字列はそれによって単に分割されます。
empty
その場合True
、デフォルトです。
str_split('[]aaa[][]bb[c', '[]')
-> '', 'aaa', '', 'bb[c'
str_split('[]aaa[][]bb[c', '[]', empty=False)
-> 'aaa', 'bb[c'
複数の区切り文字が指定されている場合、文字列はデフォルトでそれらの区切り文字の可能な限り長いシーケンスで分割されます。またはにempty
設定されている
場合True
、区切り文字間の空の文字列も含まれます。この場合の区切り文字は単一の文字のみであることに注意してください。
str_split('aaa, bb : c;', ' ', ',', ':', ';')
-> 'aaa', 'bb', 'c'
str_split('aaa, bb : c;', *' ,:;', empty=True)
-> 'aaa', '', 'bb', '', '', 'c', ''
区切り文字が指定されていない場合string.whitespace
はが使用されるため、str.split()
この関数がジェネレータであることを除いて、効果はと同じです。
str_split('aaa\\t bb c \\n')
-> 'aaa', 'bb', 'c'
import string
def _str_split_chars(s, delims):
"Split the string `s` by characters contained in `delims`, including the \
empty parts between two consecutive delimiters"
start = 0
for i, c in enumerate(s):
if c in delims:
yield s[start:i]
start = i+1
yield s[start:]
def _str_split_chars_ne(s, delims):
"Split the string `s` by longest possible sequences of characters \
contained in `delims`"
start = 0
in_s = False
for i, c in enumerate(s):
if c in delims:
if in_s:
yield s[start:i]
in_s = False
else:
if not in_s:
in_s = True
start = i
if in_s:
yield s[start:]
def _str_split_word(s, delim):
"Split the string `s` by the string `delim`"
dlen = len(delim)
start = 0
try:
while True:
i = s.index(delim, start)
yield s[start:i]
start = i+dlen
except ValueError:
pass
yield s[start:]
def _str_split_word_ne(s, delim):
"Split the string `s` by the string `delim`, not including empty parts \
between two consecutive delimiters"
dlen = len(delim)
start = 0
try:
while True:
i = s.index(delim, start)
if start!=i:
yield s[start:i]
start = i+dlen
except ValueError:
pass
if start<len(s):
yield s[start:]
def str_split(s, *delims, empty=None):
"""\
Split the string `s` by the rest of the arguments, possibly omitting
empty parts (`empty` keyword argument is responsible for that).
This is a generator function.
When only one delimiter is supplied, the string is simply split by it.
`empty` is then `True` by default.
str_split('[]aaa[][]bb[c', '[]')
-> '', 'aaa', '', 'bb[c'
str_split('[]aaa[][]bb[c', '[]', empty=False)
-> 'aaa', 'bb[c'
When multiple delimiters are supplied, the string is split by longest
possible sequences of those delimiters by default, or, if `empty` is set to
`True`, empty strings between the delimiters are also included. Note that
the delimiters in this case may only be single characters.
str_split('aaa, bb : c;', ' ', ',', ':', ';')
-> 'aaa', 'bb', 'c'
str_split('aaa, bb : c;', *' ,:;', empty=True)
-> 'aaa', '', 'bb', '', '', 'c', ''
When no delimiters are supplied, `string.whitespace` is used, so the effect
is the same as `str.split()`, except this function is a generator.
str_split('aaa\\t bb c \\n')
-> 'aaa', 'bb', 'c'
"""
if len(delims)==1:
f = _str_split_word if empty is None or empty else _str_split_word_ne
return f(s, delims[0])
if len(delims)==0:
delims = string.whitespace
delims = set(delims) if len(delims)>=4 else ''.join(delims)
if any(len(d)>1 for d in delims):
raise ValueError("Only 1-character multiple delimiters are supported")
f = _str_split_chars if empty else _str_split_chars_ne
return f(s, delims)
この関数はPython 3で機能し、2つのバージョンと3つのバージョンの両方で機能するように、簡単ではありますがかなり醜い修正を適用できます。関数の最初の行を次のように変更する必要があります。
def str_split(s, *delims, **kwargs):
"""...docstring..."""
empty = kwargs.get('empty')
いいえ。ただし、を使用して簡単に作成できitertools.takewhile()
ます。
編集:
非常にシンプルで壊れた実装:
import itertools
import string
def isplitwords(s):
i = iter(s)
while True:
r = []
for c in itertools.takewhile(lambda x: not x in string.whitespace, i):
r.append(c)
else:
if r:
yield ''.join(r)
continue
else:
raise StopIteration()
takeWhile
。predicate
文字列を単語(デフォルトsplit
)に分割するために何が良いでしょうtakeWhile()
か?
string.whitespace
。
'abc<def<>ghi<><>lmn'.split('<>') == ['abc<def', 'ghi', '', 'lmn']
のジェネレーターバージョンに明らかな利点はありませんsplit()
。ジェネレーターオブジェクトには、反復する文字列全体を含める必要があるため、ジェネレーターを使用してメモリを節約する必要はありません。
あなたがそれを書きたいなら、それはかなり簡単ですが:
import string
def gsplit(s,sep=string.whitespace):
word = []
for c in s:
if c in sep:
if word:
yield "".join(word)
word = []
else:
word.append(c)
if word:
yield "".join(word)
id()
てください。また、文字列は不変であるため、繰り返し処理中に誰かが元の文字列を変更することを心配する必要はありません。
@ninjageckoの回答のバージョンを作成しました。これはstring.splitのように動作します(つまり、デフォルトで空白が区切られており、区切り文字を指定できます)。
def isplit(string, delimiter = None):
"""Like string.split but returns an iterator (lazy)
Multiple character delimters are not handled.
"""
if delimiter is None:
# Whitespace delimited by default
delim = r"\s"
elif len(delimiter) != 1:
raise ValueError("Can only handle single character delimiters",
delimiter)
else:
# Escape, incase it's "\", "*" etc.
delim = re.escape(delimiter)
return (x.group(0) for x in re.finditer(r"[^{}]+".format(delim), string))
ここに私が使ったテストがあります(python 3とpython 2の両方で):
# Wrapper to make it a list
def helper(*args, **kwargs):
return list(isplit(*args, **kwargs))
# Normal delimiters
assert helper("1,2,3", ",") == ["1", "2", "3"]
assert helper("1;2;3,", ";") == ["1", "2", "3,"]
assert helper("1;2 ;3, ", ";") == ["1", "2 ", "3, "]
# Whitespace
assert helper("1 2 3") == ["1", "2", "3"]
assert helper("1\t2\t3") == ["1", "2", "3"]
assert helper("1\t2 \t3") == ["1", "2", "3"]
assert helper("1\n2\n3") == ["1", "2", "3"]
# Surrounding whitespace dropped
assert helper(" 1 2 3 ") == ["1", "2", "3"]
# Regex special characters
assert helper(r"1\2\3", "\\") == ["1", "2", "3"]
assert helper(r"1*2*3", "*") == ["1", "2", "3"]
# No multi-char delimiters allowed
try:
helper(r"1,.2,.3", ",.")
assert False
except ValueError:
pass
Pythonのregexモジュールは、Unicodeの空白に対して「正しいこと」を行うと言っていますが、実際にはテストしていません。
要旨としてもご利用いただけます。
イテレータを読み取ることができるようにしたい場合(そしてイテレータを返す場合も)、次のようにしてください。
import itertools as it
def iter_split(string, sep=None):
sep = sep or ' '
groups = it.groupby(string, lambda s: s != sep)
return (''.join(g) for k, g in groups if k)
使用法
>>> list(iter_split(iter("Good evening, world!")))
['Good', 'evening,', 'world!']
more_itertools.split_at
str.split
イテレータのアナログを提供します。
>>> import more_itertools as mit
>>> list(mit.split_at("abcdcba", lambda x: x == "b"))
[['a'], ['c', 'd', 'c'], ['a']]
>>> "abcdcba".split("b")
['a', 'cdc', 'a']
more_itertools
サードパーティのパッケージです。
find_iterソリューションを使用して特定の区切り文字のジェネレーターを返し、次にitertoolsのペアワイズレシピを使用して、元の分割メソッドのように実際の単語を取得する前の次の反復を構築する方法を示したいと思いました。
from more_itertools import pairwise
import re
string = "dasdha hasud hasuid hsuia dhsuai dhasiu dhaui d"
delimiter = " "
# split according to the given delimiter including segments beginning at the beginning and ending at the end
for prev, curr in pairwise(re.finditer("^|[{0}]+|$".format(delimiter), string)):
print(string[prev.end(): curr.start()])
注意:
def split_generator(f,s):
"""
f is a string, s is the substring we split on.
This produces a generator rather than a possibly
memory intensive list.
"""
i=0
j=0
while j<len(f):
if i>=len(f):
yield f[j:]
j=i
elif f[i] != s:
i=i+1
else:
yield [f[j:i]]
j=i+1
i=i+1
[f[j:i]]
、そうではないのf[j:i]
ですか?
ここに簡単な応答があります
def gen_str(some_string, sep):
j=0
guard = len(some_string)-1
for i,s in enumerate(some_string):
if s == sep:
yield some_string[j:i]
j=i+1
elif i!=guard:
continue
else:
yield some_string[j:]