【问题标题】:Is there a faster way to clean out control characters in a file?有没有更快的方法来清除文件中的控制字符?
【发布时间】:2015-07-22 01:43:54
【问题描述】:

Previously,我一直在使用下面的code snippet 清理数据

import unicodedata, re, io

all_chars = (unichr(i) for i in xrange(0x110000))
control_chars = ''.join(c for c in all_chars if unicodedata.category(c)[0] == 'C')
cc_re = re.compile('[%s]' % re.escape(control_chars))
def rm_control_chars(s): # see http://www.unicode.org/reports/tr44/#General_Category_Values
    return cc_re.sub('', s)

cleanfile = []
with io.open('filename.txt', 'r', encoding='utf8') as fin:
    for line in fin:
        line =rm_control_chars(line)
        cleanfile.append(line)

文件中有我想保留的换行符。

下面记录了cc_re.sub('', s)替换前几行所用的时间(第一列是时间,第二列是len(s)):

0.275146961212 251
0.672796010971 614
0.178567171097 163
0.200030088425 180
0.236430883408 215
0.343492984772 313
0.317672967911 290
0.160616159439 142
0.0732028484344 65
0.533437013626 468
0.260229110718 236
0.231380939484 204
0.197766065598 181
0.283867120743 258
0.229172945023 208

正如@ashwinichaudhary 所建议的那样,使用s.translate(dict.fromkeys(control_chars)) 和同一时间的日志输出:

0.464188098907 252
0.366552114487 615
0.407374858856 164
0.322507858276 181
0.35142993927 216
0.319973945618 314
0.324357032776 291
0.371646165848 143
0.354818105698 66
0.351796150208 469
0.388131856918 237
0.374715805054 205
0.363368988037 182
0.425950050354 259
0.382766962051 209

但是对于我的 1GB 文本来说,代码真的很慢。有没有其他方法可以清除受控字符?

【问题讨论】:

  • 为什么要将整个文件保存在内存中?
  • 我需要稍后进行其他处理(我需要稍后根据某些标准选择清理后的句子,然后对所选句子进行更多处理)。内存不是问题。 re.sub 是一个瓶颈
  • 您是否尝试过使用正则表达式,而只是使用标准的replace? REs 适用于复杂的模式,但我怀疑 replace 对此更有效。此外,我会尝试找到一种方法将您的原始 1GB 文本分成多个部分 - 这也应该会改进算法 很多
  • 你应该试试str.translate
  • @alvas 对于unicode.translate,应该这样做:s.translate(dict.fromkeys(control_chars))

标签: python regex unicode io control-characters


【解决方案1】:

我很惊讶没有人提到 mmap 这可能正好适合这里。

注意:我会把这个作为答案,以防它有用,并抱歉我现在没有时间实际测试和比较它。

您将文件加载到内存中(有点),然后您实际上可以在对象上运行re.sub()。这有助于消除 IO 瓶颈,并允许您在立即写回字节之前就地更改字节。

在此之后,您可以尝试使用 str.translate() 与 re.sub(),还可以包括任何进一步的优化,例如双缓冲 CPU 和 IO 或使用多个 CPU 内核/线程。

但它看起来像这样;

import mmap

f = open('test.out', 'r')
m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)

mmap 文档的一个很好的摘录是;

..您可以在大多数需要字符串的地方使用 mmap 对象;例如,您可以使用 re 模块搜索内存映射文件。因为它们是可变的,你可以通过 obj[index] = 'a',.. 来改变单个字符。

【讨论】:

    【解决方案2】:

    如果你想让它移动得非常快?将您的输入分成多个块,将该数据处理代码包装为一种方法,并使用 Python 的 multiprocessing 包将其并行化,写入一些常见的文本文件。逐个字符地处理这类东西是最简单的方法,但总是需要一段时间。

    https://docs.python.org/3/library/multiprocessing.html

    【讨论】:

      【解决方案3】:

      这必须在python中吗?在开始使用 python 读取文件之前如何清理文件。使用 sed 无论如何都会逐行处理它。

      请参阅删除 control characters using sed

      如果你把它输出到另一个文件,你可以打开它。我不知道它会有多快。您可以在 shell 脚本中执行此操作并对其进行测试。根据this page - sed 是每秒 82M 个字符。

      希望对你有帮助。

      【讨论】:

        【解决方案4】:

        与 UTF-8 一样,所有控制字符都编码为 1 个字节(与 ASCII 兼容)并且低于 32,我建议使用这种快速的代码:

        #!/usr/bin/python
        import sys
        
        ctrl_chars = [x for x in range(0, 32) if x not in (ord("\r"), ord("\n"), ord("\t"))]
        filename = sys.argv[1]
        
        with open(filename, 'rb') as f1:
          with open(filename + '.txt', 'wb') as f2:
            b = f1.read(1)
            while b != '':
              if ord(b) not in ctrl_chars:
                f2.write(b)
              b = f1.read(1)
        

        还可以吗?

        【讨论】:

        • DEL 字符是 0x7F,所以是 127,如果你把它算作控制字符,你也可以添加它。
        • 这恰当地描述了一组控制字符。
        【解决方案5】:

        我会尝试几件事。

        首先,使用替换所有正则表达式进行替换。

        其次,设置一个具有已知控制字符范围的正则表达式字符类
        一类单独的控制字符。
        (这是在引擎没有将其优化到范围的情况下。
        一个范围在汇编级别需要两个条件,
        而不是针对类中每个字符的单独条件)

        第三,由于要删除字符,请添加一个贪心量词
        课后。这将否定进入替代的必要性
        每个单个字符匹配后的子例程,而不是抓取所有相邻的字符
        如所须。

        我不知道正则表达式构造的 python 语法,
        也不是 Unicode 中的所有控制代码,但结果看起来有点像
        像这样:

        [\u0000-\u0009\u000B\u000C\u000E-\u001F\u007F]+
        

        最多的时间是将结果复制到另一个字符串。
        找到所有控制代码所需的时间最少,这
        将是微不足道的。

        在所有条件相同的情况下,正则表达式(如上所述)是最快的方法。

        【讨论】:

          【解决方案6】:

          通过字符找到了一个解决方案工作字符,我使用 100K 文件对其进行了基准标记:

          import unicodedata, re, io
          from time import time
          
          # This is to generate randomly a file to test the script
          
          from string import lowercase
          from random import random
          
          all_chars = (unichr(i) for i in xrange(0x110000))
          control_chars = [c for c in all_chars if unicodedata.category(c)[0] == 'C']
          chars = (list(u'%s' % lowercase) * 115117) + control_chars
          
          fnam = 'filename.txt'
          
          out=io.open(fnam, 'w')
          
          for line in range(1000000):
              out.write(u''.join(chars[int(random()*len(chars))] for _ in range(600)) + u'\n')
          out.close()
          
          
          # version proposed by alvas
          all_chars = (unichr(i) for i in xrange(0x110000))
          control_chars = ''.join(c for c in all_chars if unicodedata.category(c)[0] == 'C')
          cc_re = re.compile('[%s]' % re.escape(control_chars))
          def rm_control_chars(s):
              return cc_re.sub('', s)
          
          t0 = time()
          cleanfile = []
          with io.open(fnam, 'r', encoding='utf8') as fin:
              for line in fin:
                  line =rm_control_chars(line)
                  cleanfile.append(line)
          out=io.open(fnam + '_out1.txt', 'w')
          out.write(''.join(cleanfile))
          out.close()
          print time() - t0
          
          # using a set and checking character by character
          all_chars = (unichr(i) for i in xrange(0x110000))
          control_chars = set(c for c in all_chars if unicodedata.category(c)[0] == 'C')
          def rm_control_chars_1(s):
              return ''.join(c for c in s if not c in control_chars)
          
          t0 = time()
          cleanfile = []
          with io.open(fnam, 'r', encoding='utf8') as fin:
              for line in fin:
                  line = rm_control_chars_1(line)
                  cleanfile.append(line)
          out=io.open(fnam + '_out2.txt', 'w')
          out.write(''.join(cleanfile))
          out.close()
          print time() - t0
          

          输出是:

          114.625444174
          0.0149750709534
          

          我尝试了一个 1Gb 的文件(仅用于第二个),它持续了 186 秒。

          我还编写了同一脚本的另一个版本,速度稍快(176 秒),内存效率更高(对于不适合 RAM 的非常大的文件):

          t0 = time()
          out=io.open(fnam + '_out5.txt', 'w')
          with io.open(fnam, 'r', encoding='utf8') as fin:
              for line in fin:
                  out.write(rm_control_chars_1(line))
          out.close()
          print time() - t0
          

          【讨论】:

          • 你能解释一下chars = (list(u'%s' % lowercase) * 115117) + control_charsu''.join(chars[int(random()*len(chars))] for _ in range(600)) + u'\n'吗?
          • 是的,第一个生成带有小写字母和所有控制字符的字符列表(我只是将小写乘以 115117 以得到控制字符的大小)。我最终得到了一个巨大的字符列表。第二部分包括从先前的列表中随机选择给定数量的字符以构建文件。所有这一切只是为了生成一个文件来测试删除控制字符的功能有多快且仍然准确......我本可以从答案中删除它,但认为它可以帮助您检查我的解决方案或帮助其他人回答更快
          猜你喜欢
          • 1970-01-01
          • 2011-03-24
          • 1970-01-01
          • 2023-03-29
          • 2020-11-10
          • 2011-09-19
          • 1970-01-01
          • 2022-11-25
          • 1970-01-01
          相关资源
          最近更新 更多