在换行符第n次出现后,大多数pythonic分割大字符串的方式

时间:2017-12-13 01:24:56

标签: python algorithm

我有一个非常大的多行字符串,并且想要将它分成一个数组,比如说,在每个第50次出现换行符后(\ n) 在您看来,最具Pythonic和效率的方法是什么?

6 个答案:

答案 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=''))

第二种方法似乎是两者中的更快。将它们与splitlinesjoin结果切片的非内存敏感方法进行比较:

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,至少我是这么认为的; /