string.split()
zwraca instancję o numerze z numerem. Czy istnieje wersja, która zamiast tego zwraca wartość generator? Czy istnieją jakieś powody, aby nie mieć wersji generatora?Czy istnieje wersja generatora `string.split()` w języku Python?
Odpowiedz
Jest wysoce prawdopodobne, że re.finditer
wykorzystuje dość minimalne obciążenie pamięci.
def split_iter(string):
return (x.group(0) for x in re.finditer(r"[A-Za-z']+", string))
Demo:
>>> list(split_iter("A programmer's RegEx test."))
['A', "programmer's", 'RegEx', 'test']
edit: właśnie potwierdziła, że bierze stałej pamięci w python 3.2.1, zakładając, że moja metoda badania była prawidłowa. Stworzyłem ciąg bardzo dużego rozmiaru (1 GB lub więcej), a następnie iterowałem przez iterację za pomocą pętli for
(NIE Zrozumienie listy, które spowodowałoby dodatkową pamięć).Nie spowodowało to zauważalnego wzrostu pamięci (to znaczy, jeśli nastąpił wzrost pamięci, było to znacznie mniej niż ciąg 1GB).
Doskonale! Zapomniałem o założycielu. Jeśli ktoś byłby zainteresowany zrobieniem czegoś takiego jak splitlines, sugerowałbym użycie tego RE: '(. * \ N |. + $)' Str.splitlines odciąga się od szkolącego się newline'a (coś, czego naprawdę nie lubię ...); jeśli chcesz zreplikować tę część zachowania, możesz użyć grupowania: (m.group (2) lub m.group (3) dla m in re.finditer ('((. *) \ n | (. +) $) ", s)). PS: Domyślam się, że zewnętrzny paren w RE nie jest potrzebny; Po prostu czuję się nieswojo przy użyciu | bez papieru: P – allyourcode
A co z wydajnością? ponowne dopasowywanie powinno być wolniejsze niż zwykłe wyszukiwanie. –
Jak przerobiłbyś tę funkcję split_iter tak, aby działała jak 'a_string.split (" ogranicznik ")'? – Moberg
Nie, ale powinno być łatwo napisać jedną przy użyciu itertools.takewhile()
.
EDIT:
Bardzo proste, pół złamane realizacja:
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: Przykład w dokumencie używa listy liczb całkowitych do zilustrowania użycia 'takeWhile'. Jaki byłby dobry "predykat" do dzielenia ciągu na słowa (domyślnie 'split') za pomocą' takeWhile() '? –
Wyszukaj obecność w 'string.whitespace'. –
Separator może mieć wiele znaków, '' abc
nie widzę żadnych oczywistych korzyści wersji generatora z
split()
. Obiekt generatora będzie musiał zawierać cały ciąg do iteracji, więc nie będziesz zapisywać pamięci poprzez generator.
Jeśli chciałeś napisać byłoby dość łatwo jednak:
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)
Połowę użytej pamięci można zmniejszyć o połowę, ponieważ nie trzeba przechowywać drugiej kopii ciągu znaków w każdej wynikowej części, a także narzutu tablicy i obiektu (zwykle jest to więcej niż same ciągi znaków). Generalnie nie ma to znaczenia (jeśli dzielisz łańcuchy tak duże, że to ma znaczenie, prawdopodobnie robisz coś źle), a nawet natywna implementacja generatora C zawsze byłaby znacznie wolniejsza niż wykonanie wszystkiego naraz. –
@Glenn Maynard - Właśnie to sobie uświadomiłem. Z jakiegoś powodu pierwotnie generator zapisałby kopię ciągu, a nie referencję. Szybkie sprawdzenie za pomocą 'id()' mnie poprawiło. I oczywiście, ponieważ ciągi znaków są niezmienne, nie musisz się martwić, że ktoś zmienia oryginalny ciąg podczas iteracji. –
Czy głównym celem użycia generatora nie jest użycie pamięci, ale czy można oszczędzić sobie konieczności dzielenia całego ciągu znaków, jeśli chcesz wyjść wcześniej? (To nie jest komentarz do twojego konkretnego rozwiązania, byłem zaskoczony dyskusją o pamięci). –
można zbudować łatwo za pomocą samego str.split z limitem:
def isplit(s, sep=None):
while s:
parts = s.split(sep, 1)
if len(parts) == 2:
s = parts[1]
else:
s = ''
yield parts[0]
ten sposób, nie muszą replikować funkcjonalności i zachowania strip() (np. gdy sep = None) i zależy to od jego możliwie szybkiej implementacji natywnej. Zakładam, że string.split przestanie skanować ciąg znaków dla separatorów, gdy ma wystarczająco dużo "części".
Jak zauważa Glenn Maynard, skala ta jest słaba w przypadku dużych ciągów znaków (O (n^2)). Potwierdziłem to poprzez testy "timit".
To jest O (n^2), co powoduje, że jest on tragicznie wolny, gdy ciąg znaków zawiera dużo słów, np. '" abcd "* 1000000'. (Wyjaśniłem to już komuś, kto podał to samo rozwiązanie - skasował odpowiedź, więc teraz muszę powtórzyć ...) –
@Glenn: podczas gdy szkoda, że tak jasny kod nie ma dobrej złożoności Myślę, że dla łańcuchów o typowej długości byłoby dobrze. Jaka jest długość ciągów, które zazwyczaj dzielisz? – SilentGhost
Możesz także poprawić wydajność i kod używając 'partition' (która nie pozwala na separator' None'): 'while s: a, _, s = s.partition (sep); yield a' – SilentGhost
To jest wersja generatora split()
zaimplementowana za pośrednictwem re.search()
, która nie ma problemu z przydzieleniem zbyt wielu podciągów.
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: Poprawiona obsługa otaczające spacje, jeśli nie ma znaków separatora podano.
dlaczego to jest lepsze niż 're.finditer'? –
Potrzeba jest dla mnie, przynajmniej z plikami używanymi jako generatory.
To jest wersja zrobiłem w ramach przygotowań do niektórych dużych plików z pustą linią separacji bloki tekstu (ta musiałaby zostać gruntownie przetestowany w przypadkach narożnych w przypadku byłoby go używać w systemie produkcyjnym):
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)
Najbardziej efektywny sposób, w jaki można go napisać, używając parametru offset
metody str.find()
. Pozwala to uniknąć dużej ilości pamięci i opiera się na narzucie regexp, gdy nie jest potrzebne.
[edytuj 02/08/2016: updated to opcjonalnie wsparcie regex separatory]
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
ten może być używany jak chcesz ...
>>> print list(isplit("abcb","b"))
['a','c','']
Chociaż istnieje trochę poszukiwania kosztów w ciągu za każdym razem, gdy find() lub krojenie jest wykonywane, powinno to być minimalne, ponieważ łańcuchy są reprezentowane w pamięci jako ciągłe tablice.
Oto moja implementacja, która jest znacznie, dużo szybsza i bardziej kompletna niż inne odpowiedzi tutaj. Posiada 4 oddzielne podfunkcje dla różnych przypadków.
będę po prostu skopiować docstring głównego str_split
funkcji:
str_split(s, *delims, empty=None)
podzielić ciąg s
przez resztę argumentów, prawdopodobnie pomijając pustych części (empty
kluczowe argument jest odpowiedzialny za że). To jest funkcja generatora.
Gdy dostarczany jest tylko jeden ogranicznik, ciąg jest po prostu dzielony przez niego. empty
to domyślnie True
.
str_split('[]aaa[][]bb[c', '[]')
-> '', 'aaa', '', 'bb[c'
str_split('[]aaa[][]bb[c', '[]', empty=False)
-> 'aaa', 'bb[c'
Gdy wiele ograniczniki są dostarczane, ciąg jest dzielony przez najdłuższy możliwe sekwencje tych ograniczników domyślnie, lub, jeśli jest ustawiony na empty
True
, puste struny między ogranicznikami są również zawarte. Zauważ, że ograniczniki w tym przypadku mogą być pojedynczymi znakami.
str_split('aaa, bb : c;', ' ', ',', ':', ';')
-> 'aaa', 'bb', 'c'
str_split('aaa, bb : c;', *' ,:;', empty=True)
-> 'aaa', '', 'bb', '', '', 'c', ''
Gdy ma ograniczniki są dostarczane string.whitespace
jest stosowany, a więc wpływ jest taka sama jak str.split()
, oprócz tego, że jest to funkcja generatora.
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)
Ta funkcja działa w Pythonie 3 i łatwym, choć dość brzydki, Fix można zastosować, aby pracować w obu wersjach 2 i 3. Pierwsze wiersze funkcji powinno być zmienione na:
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
dlaczego dajesz '[f [j: i]] 'a nie' f [j: i] '? – Moberg
Napisałem wersję użytkownika @ ninjagecko odpowiedź, która zachowuje się bardziej jak string.split (tj spacje domyślnie ograniczona i można określić separator).
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))
Oto testów użyłem (zarówno w Pythonie 3 i Python 2): Moduł regex
# 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
Pythona mówi, że does "the right thing" dla Unicode spacji, ale nie faktycznie przetestowane.
Dostępne również jako gist.
Jeżeli chcesz też mieć możliwość czytać iterator (a także powrotną jeden) Spróbuj:
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)
Wykorzystanie
>>> list(iter_split(iter("Good evening, world!")))
['Good', 'evening,', 'world!']
zrobił kilka testów wydajności na różne proponowane metody (nie będę ich tutaj powtarzać). Niektóre wyniki:
str.split
(domyślne = 0,3461570239996945- ręczne wyszukiwanie (po znaku) (jedna odpowiedź Dave Webb użytkownika) = 0,8260340550004912
re.finditer
(odpowiedź ninjagecko w) = 0,698872097000276str.find
(jeden ocalenia odpowiedzi Collinsa) = 0,7230395330007013itertools.takewhile
(odpowiedź Ignacio Vazquez-Abrams'S) = 2,023023967998597str.split(..., maxsplit=1)
rekurencji = N/A †
† odpowiedź rekursji (string.split
z maxsplit = 1
) nie zakończyć w rozsądnym czasie, biorąc pod uwagę string.split
s prędkość mogą one działać lepiej na krótsze ciągi, ale potem nie mogę zobaczyć przypadek użycia krótkich łańcuchów, w których pamięć i tak nie jest problemem.
testowano przy użyciu timeit
na:
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
To rodzi kolejne pytanie, dlaczego string.split
jest tak dużo szybciej pomimo jego użycia pamięci.
Chciałem pokazać, jak użyć rozwiązania find_iter, aby zwrócić generator dla danych ograniczników, a następnie użyć przepisu parowania z itertools do zbudowania poprzedniej następnej iteracji, która otrzyma rzeczywiste słowa jak w oryginalnej metodzie dzielenia.
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()])
uwaga:
- Używam prev & Curr zamiast poprz & obok ponieważ nadrzędnym obok w Pythonie jest to bardzo zły pomysł
- Jest to dość skuteczny
more_itertools.spit_at
oferuje analog do str.split
dla iteratorów.
>>> 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. Czy istnieje wersja NavalableMap w Scali?
- 2. Czy istnieje wersja python dla biblioteki metryk opartej na JVM
- 3. Czy istnieje cicha wersja podprocesu?
- 4. Python wersja freopen()
- 5. Czy istnieje odpowiednik w języku Python dla polecenia "which"?
- 6. Czy w języku Python istnieje piękne środowisko State Machine?
- 7. Czy w języku Python istnieje odpowiednik wyjątku Java IllegalStateException?
- 8. Czy istnieje narzędzie do wizualizacji wywołań metod w języku Python?
- 9. Czy istnieje pakiet zalecany do uczenia maszynowego w języku Python?
- 10. Czy istnieje odpowiednik konw. MATLAB w języku Python?
- 11. Czy istnieje metoda "podzielonej listy" w języku C#?
- 12. średnia obliczeniowa w python dla generatora
- 13. Python wersja operatora warunkowego C# (?)
- 14. Python: konwertowanie lista do generatora
- 15. Czy istnieje odpowiednik String.Split, który zwraca ogólną listę?
- 16. Czy w języku Python występuje "do ... aż"?
- 17. Czy istnieje parametryczna wersja leniwego `ByteString`?
- 18. Czy istnieje standardowa, skrócona wersja memcpy?
- 19. Czy istnieje wersja kodu JavaScript String.fromCharCode?
- 20. Czy istnieje wersja log4net dla Silverlight?
- 21. Czy istnieje wersja XCode "Override/Implement Method"?
- 22. Czy istnieje wersja wchar_t dla asprintf?
- 23. sqlite3 w języku Python
- 24. Czy istnieje czysta wersja Pythona z MurmurHash?
- 25. Czy istnieje wersja $ getJSON, która nie oddzwoni?
- 26. Funkcje generatora w R
- 27. Czy istnieje sposób na skrócenie tego wyrażenia generatora Pythona?
- 28. Czy istnieje alternatywa dla QR Generatora Google Chart API?
- 29. Czy w języku Java istnieje odpowiednik epollu?
- 30. Czy istnieje odpowiednik Groovy w języku C#?
[To pytanie] (http://stackoverflow.com/questions/3054604/) może być powiązane. –
Powodem jest to, że bardzo trudno jest myśleć o przypadku, w którym jest to przydatne. Dlaczego tego chcesz? –
@ Glenn: Ostatnio zobaczyłem pytanie o podział długiego łańcucha na fragmenty n słów. Jedno z rozwiązań "podzieliło" ciąg znaków, a następnie zwróciło generator działający na wynik 'split'. To sprawiło, że zastanawiałem się, czy istnieje sposób na "split", aby zwrócić generator na początek. –