programing

파이썬에 'string.split()'의 생성기 버전이 있습니까?

elecom 2023. 8. 11. 21:31
반응형

파이썬에 'string.split()'의 생성기 버전이 있습니까?

string.split() 목록 인스턴스를 반환합니다.제너레이터를 대신 반환하는 버전이 있습니까?제너레이터 버전을 사용하지 않는 이유가 있습니까?

가 상당히 최소한의 메모리 오버헤드를 사용할 가능성이 높습니다.

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에서 일정한 메모리가 필요하다는 것을 확인했습니다.매우 큰 크기(1GB 정도)의 문자열을 생성한 다음 반복 가능한 항목을 통해for루프(추가 메모리를 생성했을 수 있는 목록 이해가 아님).이로 인해 메모리가 눈에 띄게 증가하지는 않았습니다(즉, 메모리가 증가한 경우 1GB 문자열보다 훨씬 적음).

더 일반적인 버전:

의견에 대한 답변으로 "다음과의 연결을 볼 수 없습니다.str.split보다 일반적인 버전은 다음과 같습니다.

def splitStr(string, sep="\s+"):
    # warning: does not yet work if sep is a lookahead like `(?=b)`
    if sep=='':
        return (c for c in string)
    else:
        return (_.group(1) for _ in re.finditer(f'(?:^|{sep})((?:(?!{sep}).)*)', string))
    # alternatively, more verbosely:
    regex = f'(?:^|{sep})((?:(?!{sep}).)*)'
    for match in re.finditer(regex, string):
        fragment = match.group(1)
        yield fragment

라는 생각입니다.((?!pat).)*패턴이 일치하기 시작할 때까지 그룹이 탐욕스럽게 일치하도록 함으로써 그룹을 '부정'합니다(Lookaheads는 정규식 유한 상태 기계의 문자열을 소비하지 않습니다).:함 (의코에서반 : 로으복적드사▁(▁inbegin-of-string 또는 x 또는{sep}) +as much as possible until we would be able to begin again (or hit end of string)

데모:

>>> splitStr('.......A...b...c....', sep='...')
<generator object splitStr.<locals>.<genexpr> at 0x7fe8530fb5e8>

>>> list(splitStr('A,b,c.', sep=','))
['A', 'b', 'c.']

>>> list(splitStr(',,A,b,c.,', sep=','))
['', '', 'A', 'b', 'c.', '']

>>> list(splitStr('.......A...b...c....', '\.\.\.'))
['', '', '.A', 'b', 'c', '.']

>>> list(splitStr('   A  b  c. '))
['', 'A', 'b', 'c.', '']

(St.split은 추악한 행동을 보인다는 것에 주목해야 합니다: 그것은 특별한 경우를 가지고 있습니다.sep=Nonestr.strip선행 및 후행 공백을 제거합니다.위는 의도적으로 그렇게 하지 않습니다. sep=의 마지막 예를 참조하십시오."\s+".)

(이를 구현하려고 할 때 다양한 버그(내부 오류 포함)가 발생했습니다.부정적인 뒤를 보면 고정 길이 구분자로 제한되므로 사용하지 않습니다.에 거의 모든 및 끝 위식이외거예의모의것문든케이끝에스시지문및열자은작자열규정예-▁(▁almost▁with(:케이스e▁end▁the▁edge에지끝▁seemed▁besidesge▁to▁result▁and▁errors▁incases▁re▁anything▁the▁above-자x-▁beginning위열문--)에 오류를 초래하는 것처럼 보였습니다.r'(.*?)($|,)'',,,a,,b,c'아온다를 합니다.['', '', '', 'a', '', 'b', 'c', '']끝에 불필요한 빈 문자열이 있습니다. 실제 미묘한 버그가 있는 것처럼 보이는 또 다른 정규식의 편집 기록을 볼 수 있습니다.)

(높은 성능을 위해 직접 구현하고 싶다면(비록 중량이지만, 정규식은 C에서 가장 중요하게 실행됨) 코드를 작성합니다(ctypes?).고정 길이 구분 기호에 대해 다음 유사 코드를 사용하여 생성자를 작업하는 방법을 알 수 없습니다.길이 L의 구분 기호를 해시합니다. 실행 해시 알고리즘 O(1) 업데이트 시간을 사용하여 문자열을 검색할 때 길이 L의 실행 해시를 유지합니다.해시가 구분 기호와 같을 때마다 지난 몇 개의 문자가 구분 기호인지 수동으로 확인하고, 그렇다면 마지막 양보 이후 하위 문자열을 반환합니다.문자열의 시작과 끝에 특수한 대소문자를 사용합니다.이것은 O(N) 텍스트 검색을 수행하기 위한 교과서 알고리즘의 생성기 버전입니다.다중 처리 버전도 가능합니다.그들은 과잉 살상으로 보일 수도 있지만, 그 질문은 하나가 정말 큰 힘을 가지고 일하고 있다는 것을 의미합니다.이 시점에서 바이트 오프셋을 캐슁하거나 테스트 문자열 보기 개체에서 디스크를 지원하는 작업, RAM 추가 구입 등과 같은 엄청난 작업을 고려할 수 있습니다.)

내가 생각할 수 있는 가장 효율적인 방법은 다음을 사용하여 하나를 쓰는 것입니다.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','']

찾기() 또는 슬라이싱을 수행할 때마다 문자열 내에서 약간의 비용 추구가 있지만, 메모리에서 문자열이 연속 배열로 표시되기 때문에 이는 최소화되어야 합니다.

제안된 다양한 방법에 대해 성능 테스트를 수행했습니까(여기서는 반복하지 않겠습니다).일부 결과:

  • str.split=(설정 = 0.346157023996945)
  • 수동 검색 (문자별) (Dave Webb의 답변 중 하나) = 0.8260340550004912
  • re.finditer= (닌자게대답) = 0.698872097000276
  • str.find콜린스의 중 ) = (엘리콜린답의중) = 0.7230395330007013
  • itertools.takewhile= (이그나시오-아브람스의 대답) = 2.023023967998597
  • str.split(..., maxsplit=1)= 없음= 해당 없음

는 대답을 합니다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메모리 사용에도 불구하고 훨씬 더 빠릅니다.

은 의제이버다니전의 버전입니다.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: 구분 기호 문자가 지정되지 않은 경우 주변 공백의 처리가 수정되었습니다.

여기 다른 답변보다 훨씬 빠르고 완벽한 구현이 있습니다.다른 경우에 대해 4개의 하위 기능이 있습니다.

의 문서 하겠습니다.str_split함수:


str_split(s, *delims, empty=None)

문자열 분할s부분을 할 수 있습니다. ( 머지인수의에나빈수생있다니습될략이부분해(▁by다있나니수습▁om▁(,▁parts▁possibly▁arguments▁the▁emptyitting)empty키워드 인수가 원인입니다.이것은 제너레이터 기능입니다.

구분 기호가 하나만 제공되면 문자열이 구분 기호로 분할됩니다.empty 때가그▁then입니다.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()으로 이는 generator."입니다.

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')

아니요, 하지만 그것을 사용해서 쓸 수 있을 정도로 충분히 쉬울 것입니다.

편집:

매우 단순하고 반쯤 중단된 구현:

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()

생성기 버전에 대한 명백한 이점은 없습니다. 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)

string.split과 더 유사한 동작을 하는 @ninjageco의 답변 버전을 작성했습니다(즉, 공백은 기본적으로 구분되며 구분 기호를 지정할 수 있음).

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))

다음은 제가 사용한 테스트입니다(파이썬 3과 파이썬 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

파이썬의 정규식 모듈은 유니코드 공백에 대해 "적절한 작업"을 수행한다고 하지만, 저는 실제로 테스트하지 않았습니다.

요지로도 사용할 수 있습니다.

반복기를 읽을 수 있고(반복할 수도 있음) 반환하려면 다음을 시도합니다.

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 솔루션을 사용하여 지정된 구분 기호에 대한 생성기를 반환한 다음 iter 도구의 쌍별 레시피를 사용하여 원래 분할 방법에서와 같이 실제 단어를 가져오는 이전 다음 반복을 구축하는 방법을 보여주고 싶었습니다.


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()])

참고:

  1. prev & next 대신 prev & curr를 사용합니다. 파이썬에서 다음을 재정의하는 것은 매우 나쁜 생각이기 때문입니다.
  2. 이것은 꽤 효율적입니다.

정규식/이터 도구가 없는 가장 멍청한 방법:

def isplit(text, split='\n'):
    while text != '':
        end = text.find(split)

        if end == -1:
            yield text
            text = ''
        else:
            yield text[:end]
            text = text[end + 1:]

아주 오래된 질문이지만 효율적인 알고리즘에 대한 저의 작은 기여는 다음과 같습니다.

def str_split(text: str, separator: str) -> Iterable[str]:
    i = 0
    n = len(text)
    while i <= n:
        j = text.find(separator, i)
        if j == -1:
            j = n
        yield text[i:j]
        i = j + 1
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

여기 간단한 응답이 있습니다.

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:]
def isplit(text, sep=None, maxsplit=-1):
    if not isinstance(text, (str, bytes)):
        raise TypeError(f"requires 'str' or 'bytes' but received a '{type(text).__name__}'")
    if sep in ('', b''):
        raise ValueError('empty separator')

    if maxsplit == 0 or not text:
        yield text
        return

    regex = (
        re.escape(sep) if sep is not None
        else [br'\s+', r'\s+'][isinstance(text, str)]
    )
    yield from re.split(regex, text, maxsplit=max(0, maxsplit))

다음은 분할 및 최대 분할을 기반으로 한 답변입니다.이것은 재귀를 사용하지 않습니다.

def gsplit(todo):
    chunk= 100
    while todo:
        splits = todo.split(maxsplit=chunk)
        if len(splits) == chunk:
            todo = splits.pop()
        else:
            todo=None
        for item in splits:
            yield item

언급URL : https://stackoverflow.com/questions/3862010/is-there-a-generator-version-of-string-split-in-python

반응형