我有一个非常大的多行字符串,并且想要将它分成一个数组,比如说,在每个第50次出现换行符后(\ n) 在您看来,最具Pythonic和效率的方法是什么?
答案 0 :(得分:3)
你可以使用split并连接每一个i:i + n行。不确定它是否是最pythonic方式。
data = 'one\ntwo\nthree\nfour\n'
n = 2 # in your case it will be 50
lines = data.split()
print ['\n'.join(lines[i:i+n]) for i in range(0, len(lines), n)]
结果
['one\ntwo', 'three\nfour']
答案 1 :(得分:1)
这是一种应该非常有效的方法。我不能声称它是Pythonic,如果它可能会更短。但它确实使用了yield
。
def line_groups(string, n=50):
start = 0
while start < len(string):
end = start
for i in range(n):
pos = string.find('\n', end, len(string))
if pos < 0:
end = len(string)
break
end = pos + 1
yield string[start:end]
start = end
答案 2 :(得分:1)
一些方法避免将所有行存储为内存中的单独字符串(任何split
/ splitlines
的使用都需要,由于相当高的每个对象而涉及相当多的开销每个字符串的开销)是使用类似文件的对象包装器来逐行获取行,并使用itertools.islice
(或基于聪明的zip
技巧来批量处理它们。
from io import StringIO # On Py2, for plain str, you'd use from cStringIO import StringIO
from itertools import islice
def batch_lines(data, batchsize=50):
with StringIO(data) as f:
while True:
block = ''.join(islice(f, batchsize))
if not block: break
yield block
同样地,虽然更加模糊,但您可以使用zip_longest
:
from io import StringIO # On Py2, for plain str, you'd use from cStringIOimport StringIO
from itertools import islice, zip_longest
def batch_lines(data, batchsize=50):
with StringIO(data) as f:
yield from map(''.join, zip_longest(*[f] * batchsize, fillvalue=''))
第二种方法似乎是两者中的更快。将它们与splitlines
和join
结果切片的非内存敏感方法进行比较:
def batch_lines(data, batchsize=50):
lines = data.splitlines(True)
yield from (''.join(lines[i:i+batchsize]) for i in range(0, len(lines), batchsize))
zip_longest
方法和基于splitlines
的方法对我来说大致相同(对于大量短线),而islice
方法则需要大约40%的时间。对于某些输入,zip_longest
方法比splitlines
慢,但是如果数据有大量的行(当你从行生成数百万个str
时足以导致内存压力在前面),你从内存减少中获得的收益远远超过CPU的成本。
在我的Python(Windows上的64位Python 3.6.1)上,ASCII str
的每str
开销(忽略存储实际数据的成本)为49字节(它增加了非ASCII)。因此,如果data
是1 GB的数据(包含1000万行),同时将拆分行保存在内存中将花费另外1 GB用于拆分数据,另外还有~470 MB用于与每个关联的对象标头{ {1}}(添加str
的费用来存储它们,并且您需要超过540 MB的额外开销)。如果您的系统有3 GB的RAM,并且操作系统和其他应用程序使用的是800 MB,那么额外的540 MB的成本将通过页面颠簸的痛苦减速来支付。如果您的list
小于此值,请确保使用简单方法,但如果您可能接近系统内存限制,则可能需要使用更为懒惰的方法进行分割。
答案 3 :(得分:0)
这可能不是最诡异的,但这是我对这件事的抨击:
#python 2.7
def split(string):
string_lines = string.splitlines(True)
return [sum(stringlines[i:i+50]) for i in xrange(0, len(string_lines), 50)]
它的工作原理是将字符串分成每一行,然后以50为一组抓取它们。
我被告知在字符串上使用sum
是一个非常坏主意,所以这是另一个想法:
def find_next_index(string):
for index,value in enumerate(string):
if value == '\n':
yield (index,True)
yield (len(string),False)
def split2(string):
output = []
count = 0
last_index = 0
temp = (0,True)
While temp[1]:
temp = find_next_index(string)
count += 1
if count==50:
output.append(string[last_index:temp])
last_index = temp
count = 0
return output
答案 4 :(得分:0)
这是一个简短的pythonic版本:
from itertools import zip_longest
def batch_lines(data, batchsize=50):
args = [iter(data.split())] * batchsize
return zip_longest(*args, fillvalue='')
它不会对字符串进行流处理,例如@ShadowRanger,但它更简单并且仍然包含在2行中(没有导入)。它也返回一个迭代器。该解决方案来自https://docs.python.org/3/library/itertools.html#itertools.islice
提供的grouper
函数的配方
答案 5 :(得分:0)
流输入生成器的递归算法
采取递归方式:
import sys
INTMIN = -sys.maxsize
def find_next_nth(s, sub, n=1):
cut = s.find(sub)
if cut == -1:
return INTMIN
if n == 1:
return cut
return cut + find_next_nth(s[cut+1:], sub, n=n-1) + 1
给出每个第n个切割的指数,然后在切割周围分割,如下:
def split_every_nth(s, sub, n=1):
cut = find_next_nth(s, sub, n)
while cut > 0:
yield s[:cut]
s = s[cut+1:]
cut = find_next_nth(s, sub, n)
yield s
raise StopIteration
这种方法提供了一个生成器,它也可以与作为流的s
一起使用,因为该算法不需要整个字符串s
,而只需要它的头部。非常温和的RAM。
您使用
调用该函数for chunk in split_every_nth(my_str, my_char, n=n):
#do work here
漂亮的Pythonic,至少我是这么认为的; /