【问题标题】:Fastest way to remove first and last lines from a Python string从 Python 字符串中删除第一行和最后一行的最快方法
【发布时间】:2015-03-23 22:23:55
【问题描述】:

我有一个 python 脚本,由于各种原因,它有一个相当大的字符串变量,比如 10mb 长。该字符串包含多行。

删除该字符串的第一行和最后一行的最快方法是什么?由于字符串的大小,操作越快越好;强调速度。该程序返回一个略小的字符串,没有第一行和最后一行。

'\n'.join(string_variable[-1].split('\n')[1:-1]) 是最简单的方法,但它非常慢,因为 split() 函数复制内存中的对象,而 join() 再次复制它。

示例字符串:

*** START OF DATA ***
data
data
data
*** END OF DATA ***

额外的功劳:如果中间没有数据,让这个程序不要阻塞;这是可选的,因为就我而言,不应该有一个没有数据的字符串。

【问题讨论】:

  • 您是否可以控制字符串如何进入您的程序,例如:您是否正在使用my_string = file_obj.read() 来检索字符串?另外,您需要一次在内存中显示所有行,还是一次只需要一行?

标签: python string performance


【解决方案1】:

首先在'\n' 处拆分一次,然后检查最后一个索引处的字符串是否包含'\n',如果是,则在'\n' 处检查一次str.rsplit,然后选择第0 个索引处的项目,否则返回一个空字符串:

def solve(s):
    s = s.split('\n', 1)[-1]
    if s.find('\n') == -1:
        return ''
    return s.rsplit('\n', 1)[0]
... 
>>> s = '''*** START OF DATA ***
data
data
data
*** END OF DATA ***'''
>>> solve(s)
'data\ndata\ndata'
>>> s = '''*** START OF DATA ***
*** END OF DATA ***'''
>>> solve(s)
''
>>> s = '\n'.join(['a'*100]*10**5)
>>> %timeit solve(s)
100 loops, best of 3: 4.49 ms per loop

或者根本不拆分,从任意一端找到'\n'的索引,对字符串进行切片:

>>> def solve_fast(s):
    ind1 = s.find('\n')
    ind2 = s.rfind('\n')
    return s[ind1+1:ind2]
... 
>>> s = '''*** START OF DATA ***
data
data
data
*** END OF DATA ***'''
>>> solve_fast(s)
'data\ndata\ndata'
>>> s = '''*** START OF DATA ***
*** END OF DATA ***'''
>>> solve_fast(s)
''
>>> s = '\n'.join(['a'*100]*10**5)
>>> %timeit solve_fast(s)
100 loops, best of 3: 2.65 ms per loop

【讨论】:

    【解决方案2】:

    考虑一个类似这样的字符串 s:

    s = "line1\nline2\nline3\nline4\nline5"
    

    以下代码...

    s[s.find('\n')+1:s.rfind('\n')]
    

    ...产生输出:

    'line2\nline3\nline4'
    

    因此,它是删除字符串第一行和最后一行的最短代码。我认为 .find 和 .rfind 方法除了搜索给定的字符串之外什么也不做。试试速度吧!

    【讨论】:

      【解决方案3】:

      根据您的用例使用字符串的方式,删除它的更快方法可能是不删除它。

      如果您计划按顺序访问字符串中的行,您可以构建一个生成器,该生成器跳过第一行和最后一行,同时产生正在消耗的每一行,而不是构建所有行的新副本。

      避免第一行和最后一行的一种特别方法是在不生成不必要的副本的情况下遍历字符串,即跟踪三个后续行并仅返回第二行,这样迭代将在到达最后一行之前结束行而不需要知道最后一个换行符的位置。

      下面的函数应该会给你想要的输出:

      def split_generator(s):
        # Keep track of start/end positions for three lines
        start_prev = end_prev = 0
        start = end = 0
        start_next = end_next = 0
      
        nr_lines = 0
      
        for idx, c in enumerate(s):
          if c == '\n':
            nr_lines += 1
      
            start_prev = start
            end_prev = end
            start = start_next
            end = end_next
            start_next = end_next
            end_next = idx
      
            if nr_lines >= 3:
              yield s[(start + 1) : end]
      
        # Handle the case when input string does not finish on "\n"
        if s[-1] != '\n' and nr_lines >= 2:
          yield s[(start_next+1):end_next]
      

      你不能测试它:

      print("1st example")
      for filtered_strs in split_generator('first\nsecond\nthird'):
        print(filtered_strs)
      
      print("2nd example")
      for filtered_strs in split_generator('first\nsecond\nthird\n'):
        print(filtered_strs)
      
      print("3rd example")
      for filtered_strs in split_generator('first\nsecond\nthird\nfourth'):
        print(filtered_strs)
      
      print("4th example")
      for filtered_strs in split_generator('first\nsecond\nthird\nfourth\n'):
        print(filtered_strs)
      
      print("5th example")
      for filtered_strs in split_generator('first\nsecond\nthird\nfourth\nfifth'):
        print(filtered_strs)
      

      将生成输出:

      1st example
      second
      2nd example
      second
      3rd example
      second
      third
      4th example
      second
      third
      5th example
      second
      third
      fourth
      

      请注意,这种方法的最大优点是一次只会创建一个新行,并且几乎不需要任何时间来生成第一行输出(而不是等待所有行都找到后再继续)但是,同样,这可能有用或没有用,具体取决于您的用例。

      【讨论】:

        【解决方案4】:

        另一种方法是在换行符处拆分数据,然后重新加入除第一行和最后一行之外的所有内容:

        >>> s = '*** START OF DATA *** \n\
        ... data\n\
        ... data\n\
        ... data\n\
        ... *** END OF DATA ***'
        >>> '\n'.join(s.split('\n')[1:-1])
        'data\ndata\ndata'
        

        这在没有数据的情况下工作正常:

        >>> s = '*** START OF DATA *** \n\
        ... *** END OF DATA ***'
        >>> '\n'.join(s.split('\n')[1:-1])
        ''
        

        【讨论】:

        • 正如 OP 所指出的,这在大数据上会非常慢。
        猜你喜欢
        • 1970-01-01
        • 2013-09-12
        • 1970-01-01
        • 2011-03-14
        • 2019-05-11
        • 2011-04-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多