Alasannya adalah sangat sulit untuk memikirkan kasus yang berguna. Kenapa kamu menginginkan ini?
Glenn Maynard
10
@ Glenn: Baru-baru ini saya melihat pertanyaan tentang membagi string panjang menjadi beberapa n kata. Salah satu solusi splitstring dan kemudian generator kembali bekerja pada hasil split. Itu membuat saya berpikir apakah ada cara untuk splitmengembalikan generator untuk memulai.
@GlennMaynard dapat berguna untuk penguraian string / file yang sangat besar, tetapi siapa pun dapat menulis parser generator sendiri dengan sangat mudah menggunakan DFA dan hasil yang dibuat sendiri
Dmitry Ponyatov
Jawaban:
77
Sangat mungkin bahwa re.finditermenggunakan overhead memori yang cukup minimal.
def split_iter(string):return(x.group(0)for x in re.finditer(r"[A-Za-z']+", string))
edit: Saya baru saja mengonfirmasi bahwa ini membutuhkan memori konstan di python 3.2.1, dengan asumsi metodologi pengujian saya benar. Saya membuat string dengan ukuran yang sangat besar (1GB atau lebih), kemudian mengulang melalui iterable dengan forloop (BUKAN pemahaman daftar, yang akan menghasilkan memori tambahan). Ini tidak menghasilkan pertumbuhan memori yang nyata (yaitu, jika ada pertumbuhan memori, itu jauh lebih kecil daripada string 1GB).
Luar biasa! Saya sudah lupa tentang finditer. Jika seseorang tertarik untuk melakukan sesuatu seperti splitlines, saya akan menyarankan menggunakan RE ini: '(. * \ N |. + $)' Str.splitlines memotong baris baru pelatihan (sesuatu yang saya tidak terlalu suka ... ); jika Anda ingin mereplikasi bagian perilaku tersebut, Anda dapat menggunakan pengelompokan: (m.group (2) atau m.group (3) untuk m di re.finditer ('((. *) \ n | (. +) $) ', s)). PS: Saya kira kurung luar di RE tidak diperlukan; Aku hanya merasa tidak nyaman menggunakan | tanpa tanda kurung: P
allyourcode
3
Bagaimana dengan kinerja? pencocokan ulang harus lebih lambat dari pencarian biasa.
anatoly techtonik
1
Bagaimana Anda menulis ulang fungsi split_iter ini agar berfungsi a_string.split("delimiter")?
Moberg
split menerima ekspresi reguler jadi tidak terlalu cepat, jika Anda ingin menggunakan nilai yang dikembalikan dengan cara selanjutnya, lihat jawaban saya di bagian bawah ...
Veltzer Doron
str.split()tidak menerima ekspresi reguler, itulah yang re.split()Anda pikirkan ...
alexis
17
Cara paling efisien yang dapat saya pikirkan untuk menulis satu menggunakan offsetparameter str.find()metode. Hal ini menghindari banyak penggunaan memori, dan mengandalkan overhead regexp saat tidak diperlukan.
[edit 2016-8-2: memperbarui ini untuk mendukung pemisah regex secara opsional]
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 isNone:# mimic default python behavior
source = source.strip()
sep ="\\s+"if isinstance(source, bytes):
sep = sep.encode("ascii")
regex =Trueif regex:# version using re.finditer()ifnot hasattr(sep,"finditer"):
sep = re.compile(sep)
start =0for 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 =0whileTrue:
idx = source.find(sep, start)if idx ==-1:yield source[start:]returnyield source[start:idx]
start = idx + sepsize
Ini dapat digunakan seperti yang Anda inginkan ...
>>>print list(isplit("abcb","b"))['a','c','']
Meskipun ada sedikit biaya pencarian dalam string setiap kali find () atau slicing dilakukan, ini harus minimal karena string direpresentasikan sebagai array yang bersebelahan dalam memori.
@ErikKaplun Karena logika regex untuk item bisa lebih kompleks daripada pemisahnya. Dalam kasus saya, saya ingin memproses setiap baris satu per satu, jadi saya dapat melaporkan kembali jika satu baris gagal untuk mencocokkan.
rovyko
9
Melakukan beberapa pengujian kinerja pada berbagai metode yang diusulkan (saya tidak akan mengulanginya di sini). Beberapa hasil:
str.split (default = 0,3461570239996945
pencarian manual (menurut karakter) (salah satu jawaban Dave Webb) = 0.8260340550004912
† Jawaban rekursi ( string.splitdengan maxsplit = 1) gagal diselesaikan dalam waktu yang wajar, mengingat string.splitkecepatannya, jawaban tersebut dapat berfungsi lebih baik pada string yang lebih pendek, tetapi saya tidak dapat melihat kasus penggunaan untuk string pendek di mana memori bukanlah masalah.
Diuji menggunakan timeitpada:
the_text ="100 "*9999+"100"def test_function( method ):def fn():
total =0for x in method( the_text ):
total += int( x )return total
return fn
Ini menimbulkan pertanyaan lain mengapa string.splitjauh lebih cepat meskipun menggunakan memori.
Ini karena memori lebih lambat daripada cpu dan dalam kasus ini, daftar dimuat oleh potongan di mana semua yang lain dimuat elemen demi elemen. Pada catatan yang sama, banyak akademisi akan memberi tahu Anda daftar tertaut lebih cepat dan memiliki lebih sedikit kerumitan sementara komputer Anda akan sering lebih cepat dengan array, yang menurutnya lebih mudah untuk dioptimalkan. Anda tidak dapat berasumsi bahwa suatu opsi lebih cepat dari yang lain, ujilah! 1 untuk pengujian.
Benoît P
Masalah muncul pada langkah selanjutnya dari rantai pemrosesan. Jika Anda kemudian ingin mencari potongan tertentu dan mengabaikan sisanya saat Anda menemukannya, Anda memiliki alasan untuk menggunakan pemisahan berbasis generator dan bukan solusi bawaan.
jgomo3
6
Inilah implementasi saya, yang jauh, jauh lebih cepat dan lebih lengkap daripada jawaban lain di sini. Ini memiliki 4 subfungsi terpisah untuk kasus yang berbeda.
Saya hanya akan menyalin docstring dari str_splitfungsi utama :
str_split(s,*delims, empty=None)
Pisahkan string sdengan sisa argumen, mungkin menghilangkan bagian kosong (empty argumen kata kunci bertanggung jawab untuk itu). Ini adalah fungsi generator.
Jika hanya satu pembatas yang diberikan, string akan dipisahkan dengannya.
emptykemudian Truesecara default.
Ketika beberapa pembatas disediakan, string dipisahkan dengan urutan terpanjang dari pembatas tersebut secara default, atau, jika emptydiatur ke
True, string kosong antara pembatas juga disertakan. Perhatikan bahwa pembatas dalam kasus ini hanya boleh satu karakter.
Jika tidak ada pembatas yang disuplai, string.whitespacedigunakan, sehingga efeknya sama str.split(), kecuali fungsi ini adalah 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 =0for i, c in enumerate(s):if c in delims:yield s[start:i]
start = i+1yield 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 =Falsefor i, c in enumerate(s):if c in delims:if in_s:yield s[start:i]
in_s =Falseelse:ifnot 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 =0try:whileTrue:
i = s.index(delim, start)yield s[start:i]
start = i+dlen
exceptValueError:passyield 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 =0try:whileTrue:
i = s.index(delim, start)if start!=i:yield s[start:i]
start = i+dlen
exceptValueError:passif 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 isNoneor empty else _str_split_word_ne
return f(s, delims[0])if len(delims)==0:
delims = string.whitespace
delims = set(delims)if len(delims)>=4else''.join(delims)if any(len(d)>1for d in delims):raiseValueError("Only 1-character multiple delimiters are supported")
f = _str_split_chars if empty else _str_split_chars_ne
return f(s, delims)
Fungsi ini bekerja di Python 3, dan perbaikan yang mudah, meskipun cukup jelek, dapat diterapkan untuk membuatnya berfungsi di versi 2 dan 3. Baris pertama dari fungsi tersebut harus diubah menjadi:
Tidak, tetapi seharusnya cukup mudah untuk menulis satu menggunakan itertools.takewhile() .
EDIT:
Penerapan yang sangat sederhana dan setengah rusak:
import itertools
import string
def isplitwords(s):
i = iter(s)whileTrue:
r =[]for c in itertools.takewhile(lambda x:not x in string.whitespace, i):
r.append(c)else:if r:yield''.join(r)continueelse:raiseStopIteration()
@Ignacio: Contoh dalam dokumen menggunakan daftar bilangan bulat untuk menggambarkan penggunaan takeWhile. Apa yang bagus predicateuntuk memisahkan string menjadi kata-kata (default split) menggunakan takeWhile()?
Manoj Govindan
Cari keberadaan di string.whitespace.
Ignacio Vazquez-Abrams
Pemisah dapat memiliki banyak karakter,'abc<def<>ghi<><>lmn'.split('<>') == ['abc<def', 'ghi', '', 'lmn']
kennytm
@Ignacio: Bisakah Anda menambahkan contoh pada jawaban Anda?
Manoj Govindan
1
Mudah untuk menulis, tetapi banyak lipat lebih lambat. Ini adalah operasi yang benar-benar harus diterapkan dalam kode asli.
Glenn Maynard
3
Saya tidak melihat manfaat yang jelas pada versi generator split() . Objek generator harus berisi seluruh string untuk mengulang sehingga Anda tidak akan menghemat memori dengan memiliki generator.
Jika Anda ingin menulisnya, itu akan cukup mudah:
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)
Anda akan membagi separuh memori yang digunakan, dengan tidak harus menyimpan salinan kedua dari string di setiap bagian yang dihasilkan, ditambah overhead array dan objek (yang biasanya lebih dari string itu sendiri). Itu umumnya tidak masalah, meskipun (jika Anda membagi string begitu besar sehingga ini penting, Anda mungkin melakukan sesuatu yang salah), dan bahkan implementasi generator C asli akan selalu jauh lebih lambat daripada melakukannya sekaligus.
Glenn Maynard
@ Glenn Maynard - Saya baru menyadarinya. Saya untuk beberapa alasan saya awalnya generator akan menyimpan salinan string daripada referensi. Pemeriksaan cepat dengan id()membuat saya benar. Dan jelas karena string tidak dapat diubah, Anda tidak perlu khawatir tentang seseorang yang mengubah string asli saat Anda mengulanginya.
Dave Webb
6
Bukankah poin utama dalam menggunakan generator bukanlah penggunaan memori, tetapi Anda dapat menyelamatkan diri Anda sendiri karena harus membagi seluruh string jika Anda ingin keluar lebih awal? (Itu bukan komentar tentang solusi khusus Anda, saya hanya terkejut dengan diskusi tentang memori).
Scott Griffiths
@Scott: Sulit untuk memikirkan kasus di mana itu benar-benar menang - di mana 1: Anda ingin berhenti membelah di tengah jalan, 2: Anda tidak tahu berapa banyak kata yang Anda pisahkan sebelumnya, 3: Anda memiliki string yang cukup besar untuk menjadi masalah, dan 4: Anda secara konsisten berhenti cukup awal agar itu menjadi kemenangan yang signifikan atas str.split. Itu adalah serangkaian kondisi yang sangat sempit.
Glenn Maynard
4
Anda bisa mendapatkan keuntungan yang jauh lebih tinggi jika string Anda dibuat dengan malas juga (misalnya dari lalu lintas jaringan atau file dibaca)
Lie Ryan
3
Saya menulis versi jawaban @ ninjagecko yang berperilaku lebih seperti string.split (yaitu dipisahkan spasi putih secara default dan Anda dapat menentukan pembatas).
def isplit(string, delimiter =None):"""Like string.split but returns an iterator (lazy)
Multiple character delimters are not handled.
"""if delimiter isNone:# Whitespace delimited by default
delim = r"\s"elif len(delimiter)!=1:raiseValueError("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))
Berikut adalah tes yang saya gunakan (di python 3 dan python 2):
# Wrapper to make it a listdef helper(*args,**kwargs):return list(isplit(*args,**kwargs))# Normal delimitersassert helper("1,2,3",",")==["1","2","3"]assert helper("1;2;3,",";")==["1","2","3,"]assert helper("1;2 ;3, ",";")==["1","2 ","3, "]# Whitespaceassert 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 droppedassert helper(" 1 2 3 ")==["1","2","3"]# Regex special charactersassert helper(r"1\2\3","\\")==["1","2","3"]assert helper(r"1*2*3","*")==["1","2","3"]# No multi-char delimiters allowedtry:
helper(r"1,.2,.3",",.")assertFalseexceptValueError:pass
Modul regex python mengatakan bahwa ia melakukan "hal yang benar" untuk whitespace unicode, tetapi saya belum benar-benar mengujinya.
Jika Anda juga ingin bisa membaca iterator (serta mengembalikannya ) coba ini:
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)
Perhatikan bahwa more_itertools.split_at () masih menggunakan daftar yang baru dialokasikan pada setiap panggilan, jadi meskipun ini mengembalikan iterator, itu tidak mencapai persyaratan memori konstan. Jadi tergantung pada mengapa Anda menginginkan iterator untuk memulai, ini mungkin atau mungkin tidak membantu.
jcater
@jater Poin yang bagus. Nilai antara memang di-buffer sebagai sub list di dalam iterator, menurut implementasinya . Seseorang dapat menyesuaikan sumber untuk mengganti daftar dengan iterator, menambahkan itertools.chaindan mengevaluasi hasil menggunakan pemahaman daftar. Bergantung pada kebutuhan dan permintaan, saya dapat memposting contoh.
pylang
2
Saya ingin menunjukkan bagaimana menggunakan solusi find_iter untuk mengembalikan generator untuk pembatas yang diberikan dan kemudian menggunakan resep berpasangan dari itertools untuk membangun iterasi berikutnya sebelumnya yang akan mendapatkan kata-kata yang sebenarnya seperti pada metode split asli.
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 endfor prev, curr in pairwise(re.finditer("^|[{0}]+|$".format(delimiter), string)):print(string[prev.end(): curr.start()])
catatan:
Saya menggunakan prev & curr daripada prev & next karena mengganti next di python adalah ide yang sangat buruk
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=0while j<len(f):if i>=len(f):yield f[j:]
j=i
elif f[i]!= s:
i=i+1else:yield[f[j:i]]
j=i+1
i=i+1
split
string dan kemudian generator kembali bekerja pada hasilsplit
. Itu membuat saya berpikir apakah ada cara untuksplit
mengembalikan generator untuk memulai.Jawaban:
Sangat mungkin bahwa
re.finditer
menggunakan overhead memori yang cukup minimal.Demo:
edit: Saya baru saja mengonfirmasi bahwa ini membutuhkan memori konstan di python 3.2.1, dengan asumsi metodologi pengujian saya benar. Saya membuat string dengan ukuran yang sangat besar (1GB atau lebih), kemudian mengulang melalui iterable dengan
for
loop (BUKAN pemahaman daftar, yang akan menghasilkan memori tambahan). Ini tidak menghasilkan pertumbuhan memori yang nyata (yaitu, jika ada pertumbuhan memori, itu jauh lebih kecil daripada string 1GB).sumber
a_string.split("delimiter")
?str.split()
tidak menerima ekspresi reguler, itulah yangre.split()
Anda pikirkan ...Cara paling efisien yang dapat saya pikirkan untuk menulis satu menggunakan
offset
parameterstr.find()
metode. Hal ini menghindari banyak penggunaan memori, dan mengandalkan overhead regexp saat tidak diperlukan.[edit 2016-8-2: memperbarui ini untuk mendukung pemisah regex secara opsional]
Ini dapat digunakan seperti yang Anda inginkan ...
Meskipun ada sedikit biaya pencarian dalam string setiap kali find () atau slicing dilakukan, ini harus minimal karena string direpresentasikan sebagai array yang bersebelahan dalam memori.
sumber
Ini adalah versi generator yang
split()
diimplementasikan melaluire.search()
yang tidak memiliki masalah dalam mengalokasikan terlalu banyak substring.EDIT: Perbaikan penanganan spasi di sekitar jika tidak ada karakter pemisah yang diberikan.
sumber
re.finditer
?Melakukan beberapa pengujian kinerja pada berbagai metode yang diusulkan (saya tidak akan mengulanginya di sini). Beberapa hasil:
str.split
(default = 0,3461570239996945re.finditer
(jawaban ninjagecko) = 0.698872097000276str.find
(salah satu jawaban Eli Collins) = 0.7230395330007013itertools.takewhile
(Jawaban Ignacio Vazquez-Abrams) = 2.023023967998597str.split(..., maxsplit=1)
rekursi = N / A †† Jawaban rekursi (
string.split
denganmaxsplit = 1
) gagal diselesaikan dalam waktu yang wajar, mengingatstring.split
kecepatannya, jawaban tersebut dapat berfungsi lebih baik pada string yang lebih pendek, tetapi saya tidak dapat melihat kasus penggunaan untuk string pendek di mana memori bukanlah masalah.Diuji menggunakan
timeit
pada:Ini menimbulkan pertanyaan lain mengapa
string.split
jauh lebih cepat meskipun menggunakan memori.sumber
Inilah implementasi saya, yang jauh, jauh lebih cepat dan lebih lengkap daripada jawaban lain di sini. Ini memiliki 4 subfungsi terpisah untuk kasus yang berbeda.
Saya hanya akan menyalin docstring dari
str_split
fungsi utama :Pisahkan string
s
dengan sisa argumen, mungkin menghilangkan bagian kosong (empty
argumen kata kunci bertanggung jawab untuk itu). Ini adalah fungsi generator.Jika hanya satu pembatas yang diberikan, string akan dipisahkan dengannya.
empty
kemudianTrue
secara default.Ketika beberapa pembatas disediakan, string dipisahkan dengan urutan terpanjang dari pembatas tersebut secara default, atau, jika
empty
diatur keTrue
, string kosong antara pembatas juga disertakan. Perhatikan bahwa pembatas dalam kasus ini hanya boleh satu karakter.Jika tidak ada pembatas yang disuplai,
string.whitespace
digunakan, sehingga efeknya samastr.split()
, kecuali fungsi ini adalah generator.Fungsi ini bekerja di Python 3, dan perbaikan yang mudah, meskipun cukup jelek, dapat diterapkan untuk membuatnya berfungsi di versi 2 dan 3. Baris pertama dari fungsi tersebut harus diubah menjadi:
sumber
Tidak, tetapi seharusnya cukup mudah untuk menulis satu menggunakan
itertools.takewhile()
.EDIT:
Penerapan yang sangat sederhana dan setengah rusak:
sumber
takeWhile
. Apa yang baguspredicate
untuk memisahkan string menjadi kata-kata (defaultsplit
) menggunakantakeWhile()
?string.whitespace
.'abc<def<>ghi<><>lmn'.split('<>') == ['abc<def', 'ghi', '', 'lmn']
Saya tidak melihat manfaat yang jelas pada versi generatorsplit()
. Objek generator harus berisi seluruh string untuk mengulang sehingga Anda tidak akan menghemat memori dengan memiliki generator.Jika Anda ingin menulisnya, itu akan cukup mudah:
sumber
id()
membuat saya benar. Dan jelas karena string tidak dapat diubah, Anda tidak perlu khawatir tentang seseorang yang mengubah string asli saat Anda mengulanginya.Saya menulis versi jawaban @ ninjagecko yang berperilaku lebih seperti string.split (yaitu dipisahkan spasi putih secara default dan Anda dapat menentukan pembatas).
Berikut adalah tes yang saya gunakan (di python 3 dan python 2):
Modul regex python mengatakan bahwa ia melakukan "hal yang benar" untuk whitespace unicode, tetapi saya belum benar-benar mengujinya.
Juga tersedia sebagai intinya .
sumber
Jika Anda juga ingin bisa membaca iterator (serta mengembalikannya ) coba ini:
Pemakaian
sumber
more_itertools.split_at
menawarkan analog kestr.split
untuk iterator.more_itertools
adalah paket pihak ketiga.sumber
itertools.chain
dan mengevaluasi hasil menggunakan pemahaman daftar. Bergantung pada kebutuhan dan permintaan, saya dapat memposting contoh.Saya ingin menunjukkan bagaimana menggunakan solusi find_iter untuk mengembalikan generator untuk pembatas yang diberikan dan kemudian menggunakan resep berpasangan dari itertools untuk membangun iterasi berikutnya sebelumnya yang akan mendapatkan kata-kata yang sebenarnya seperti pada metode split asli.
catatan:
sumber
Metode terbodoh, tanpa regex / itertools:
sumber
sumber
[f[j:i]]
dan tidakf[j:i]
?berikut adalah tanggapan sederhana
sumber