string.split()
возвращает экземпляр. Есть ли версия, которая возвращает generator? Существуют ли какие-либо причины против наличия версии генератора?Есть ли версия генератора `string.split()` в Python?
ответ
Весьма вероятно, что 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']
редактировать: Я только что подтвердил, что это требует постоянной памяти в питон 3.2.1, предполагая, что моя методика тестирования была правильной. Я создал строку очень большого размера (1 ГБ или около того), а затем повторил ее с помощью цикла с нулевым циклом for
(НЕ было понятным списком, которое создало бы дополнительную память).Это не привело к заметному росту памяти (т. Е. При росте памяти она была намного меньше, чем строка 1 ГБ).
Отлично! Я забыл о находчиве. Если бы кто-то интересовался тем, что делал что-то вроде разделенных линий, я бы предложил использовать этот RE: '(. * \ N |. + $)' Str.splitlines отрубает новую строку перевода (что-то мне не очень нравится ...); если вы хотите воспроизвести эту часть поведения, вы можете использовать группировку: (m.group (2) или m.group (3) для m в re.finditer ('((. *) \ n | (. +) $) ', s)). PS: Я думаю, что внешний паран в RE не нужен; Я просто чувствую себя неловко в использовании | без пароля: P – allyourcode
Как насчет производительности? повторное сопоставление должно быть медленнее обычного поиска. –
Как бы вы переписали эту функцию split_iter, чтобы работать как 'a_string.split (" delimiter ")'? – Moberg
Нет, но должно быть достаточно легко написать один, используя itertools.takewhile()
.
EDIT:
Очень простой, наполовину сломана реализация:
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()
@Ignacio: В примере в документах используется список целых чисел, чтобы проиллюстрировать использование 'takeWhile'. Что было бы хорошим предикатом для разделения строки на слова (по умолчанию 'split') с помощью' takeWhile() '? –
Ищите присутствие в 'string.whitespace'. –
Разделитель может иметь несколько символов, ''abc
Я не вижу какой-либо очевидной выгоды для версии генератора
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)
Вы сократили бы половину используемой памяти, не сохраняя вторую копию строки в каждой результирующей части, а также накладные расходы массива и объекта (как правило, больше, чем сами строки). Это вообще не имеет значения, хотя (если вы разделяете строки настолько большие, что это важно, вы, вероятно, делаете что-то не так), и даже встроенная реализация генератора C всегда будет значительно медленнее, чем делать все сразу. –
@Glenn Maynard - Я просто это понял. По какой-то причине я изначально генератор сохранил копию строки, а не ссылку. Быстрая проверка с помощью 'id()' верна мне. И, очевидно, поскольку строки неизменяемы, вам не нужно беспокоиться о том, что кто-то меняет исходную строку, пока вы ее итерации. –
Разве это не главное в использовании генератора, а не в использовании памяти, но что вы могли бы сэкономить себе, чтобы разделить целую строку, если вы хотите выйти раньше? (Это не комментарий вашего конкретного решения, меня просто удивило обсуждение памяти). –
Вы можете построить легко, используя саму str.split с лимитом:
def isplit(s, sep=None):
while s:
parts = s.split(sep, 1)
if len(parts) == 2:
s = parts[1]
else:
s = ''
yield parts[0]
Таким образом, вы не нужно копировать функциональность и поведение strip() (например, когда sep = None), и это зависит от возможной быстрой реализации на месте. Я предполагаю, что string.split прекратит сканирование строки для разделителей, когда у нее будет достаточно «частей».
Как указывает Гленн Мейнард, это плохо масштабируется для больших строк (O (n^2)). Я подтвердил это с помощью тестов «timit».
Это O (n^2), что делает его катастрофически медленным, когда строка содержит много слов, например. '" abcd "* 1000000'. (Я объяснил это уже кому-то, кто дал такое же решение - он удалил ответ, так что теперь я снова повторяюсь ...) –
@Glenn: хотя жаль, что такой четкий код не имеет хорошей сложности , Я бы подумал, что для строк типичной длины это будет прекрасно. Какова длина строк, которые вы обычно раскалываете? – SilentGhost
Кроме того, вы можете улучшить производительность и код с помощью 'partition' (который не допускает разделителя' None'): 'while s: a, _, s = s.partition (sep); yield a' – SilentGhost
Это версия генератора 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('][')
EDIT: Исправлена обработка окружающих пустое пространство, если не разделительные символы не приведены.
Почему это лучше, чем 're.finditer'? –
Нужна для меня, по крайней мере, файлы, используемые в качестве генераторов.
Это версия, которую я сделал в подготовке некоторых больших файлов с пустой строкой отделившихся блоки текста (это необходимо будет тщательно протестированы для случаев угловых в случае, если бы его использовать в производственной системе):
from __future__ import print_function
def isplit(iterable, sep=None):
r = ''
for c in iterable:
r += c
if sep is None:
if not c.strip():
r = r[:-1]
if r:
yield r
r = ''
elif r.endswith(sep):
r=r[:-len(sep)]
yield r
r = ''
if r:
yield r
def read_blocks(filename):
"""read a file as a sequence of blocks separated by empty line"""
with open(filename) as ifh:
for block in isplit(ifh, '\n\n'):
yield block.splitlines()
if __name__ == "__main__":
for lineno, block in enumerate(read_blocks("logfile.txt"), 1):
print(lineno,':')
print('\n'.join(block))
print('-'*40)
print('Testing skip with None.')
for word in isplit('\tTony \t Jarkko \n Veijalainen\n'):
print(word)
Самый эффективный способ, я могу думать об этом, чтобы написать один, используя параметр 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() или разрезании, это должно быть минимальным, поскольку строки представлены в виде континуальных массивов в памяти.
Вот моя реализация, которая намного, намного быстрее и полнее, чем другие ответы здесь. Он имеет 4 отдельные подфункции для разных случаев.
Я просто скопировать строку документации главного str_split
функции:
str_split(s, *delims, empty=None)
Разделить строку s
от остальных аргументов, возможно, опуская пустые части (empty
ключевое слово аргумент отвечает за что). Это функция генератора.
Когда задан только один разделитель, строка просто разделяется им. 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')
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] '? – Moberg
Я написал версию @ 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
питона говорит, что does "the right thing" для юникода пробелов, но я на самом деле не проверял.
Также доступен как gist.
Если вы хотели бы также, чтобы иметь возможность чтения итератор (а также возвращение один) попробуйте это:
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!']
ли некоторые тесты производительности на различные предложенные методы (я не буду их повторять здесь). Некоторые результаты:
str.split
(по умолчанию = 0,3461570239996945- ручной поиск (по характеру) (один из ответа Дэйва Уэбба-х) = 0,8260340550004912
re.finditer
(ответ ninjagecko в) = 0,698872097000276str.find
(один Илая ответы Коллинза) = 0,7230395330007013itertools.takewhile
(ответ Ignacio Vazquez-Абрамса) = 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
так намного быстрее, несмотря на использование памяти.
Я хотел показать, как использовать решение find_iter для возврата генератора для заданных разделителей, а затем использовать парный рецепт из itertools для построения предыдущей следующей итерации, которая будет получать фактические слова, как в исходном методе split.
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()])
примечание:
- Я использую ПРЕД & Curr вместо пред & следующий, потому что переопределение рядом с питоном очень плохая идея
- Это довольно эффективный
more_itertools.spit_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']
- 1. Есть ли ленивый `String.Split` в C#
- 2. Есть ли эквивалент носового генератора в junit?
- 3. Есть ли версия python node-webkit
- 4. Есть ли версия python для JavaScript String.fromCharCode?
- 5. python: есть ли синтаксический анализатор XML, реализованный в качестве генератора?
- 6. Есть ли способ подкласса генератора в Python 3?
- 7. Есть ли String.Split(), который учитывает StringComparison?
- 8. есть ли последняя версия pyUIQ?
- 9. Есть ли версия famo.us
- 10. Создание генератора в python
- 11. Python 3.x: проверьте, есть ли у генератора оставшиеся элементы
- 12. Старая версия генератора Javadoc Eclipse?
- 13. Есть ли способ сократить это выражение генератора Python?
- 14. Есть ли рекурсивная версия встроенного в dict.get() Python?
- 15. Репликация поведения функции string.split() Python в Qt
- 16. Есть ли версия Py2Exe Поддержка модуля MySQLdb?
- 17. Есть ли генератор кода генератора Maven?
- 18. Есть ли версия Mac DBAccess?
- 19. Есть ли x64-версия MsStkPrp.dll
- 20. Есть ли обновленная версия FolderBrowserDialog?
- 21. Есть ли версия Cype CTypeDynamic?
- 22. Есть ли пользовательская версия jQuery?
- 23. Есть ли инвертированная версия numpy.all()?
- 24. Есть ли неизменная версия Object.assign?
- 25. Есть ли асинхронная версия PLINQ?
- 26. Есть ли безопасная версия Select()?
- 27. Есть ли общая версия Array.newInstance?
- 28. Есть ли минимальная версия php5ts.dll?
- 29. Есть ли версия Concordion .NET?
- 30. Функции генератора в Python
[Этот вопрос] (http://stackoverflow.com/questions/3054604/) может быть связан. –
Причина в том, что очень сложно придумать случай, когда это полезно. Почему вы хотите этого? –
@Glenn: Недавно я увидел вопрос о разделении длинной строки на куски n слов. Одно из решений «разделило» строку, а затем вернуло генератор, работающий над результатом «split». Это заставило меня подумать, есть ли способ «сплит» вернуть генератор для начала. –