Есть ли более быстрый способ очистки управляющих символов в файле?

Ранее я очищал данные, используя фрагмент кода ниже

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) заменить первые несколько строк (1-й столбец - это время, а 2-й столбец - 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

Но код очень медленный для моего 1 ГБ текста. Есть ли другой способ убрать контролируемых персонажей?

6 ответов

Решение

Я нашел решение, работающее по характеру с помощью символов, я отметил его с помощью файла размером 100 КБ:

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 с) и более эффективную память (для очень больших файлов, не помещающихся в ОЗУ):

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

Как и в 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)

Это достаточно хорошо?

Это должно быть в питоне? Как насчет очистки файла, прежде чем читать его на python? Используйте sed, который все равно будет обрабатывать его построчно.

Смотрите удаление управляющих символов с помощью sed.

и если вы передадите его в другой файл, вы можете открыть его. Хотя я не знаю, как быстро это будет. Вы можете сделать это в сценарии оболочки и протестировать его. Согласно этой странице, Sed - это 82M символов в секунду.

Надеюсь, поможет.

Если вы хотите, чтобы он двигался очень быстро? Разбейте входные данные на несколько частей, оберните этот код для обработки данных как метод и используйте Python. multiprocessing пакет для его распараллеливания, запись в какой-нибудь общий текстовый файл. Переход от символа к символу - это самый простой метод для обработки подобных вещей, но это всегда занимает некоторое время.

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

Я удивлен, что никто не упомянул mmap, который мог бы быть здесь подходящим.

Примечание: я добавлю это в качестве ответа на случай, если это будет полезно, и извинюсь, что у меня нет времени, чтобы на самом деле протестировать и сравнить это прямо сейчас.

Вы загружаете файл в память (вид), а затем вы можете запустить re.sub() над объектом. Это помогает устранить узкое место ввода-вывода и позволяет изменить байты на месте перед тем, как записать их обратно.

После этого вы можете поэкспериментировать с str.translate() vs 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',..

Я бы попробовал пару вещей.

Сначала сделайте замену регулярным выражением замены всех.

Во-вторых, вместо этого установите класс регулярного выражения с известными диапазонами контрольных символов.
класса индивидуального контроля символов.
(Это если двигатель не оптимизирует его до диапазонов.
Диапазон требует двух условий на уровне сборки,
в отличие от отдельных условных на каждого персонажа в классе)

В-третьих, поскольку вы удаляете символы, добавьте жадный квантификатор
после урока. Это сведет на нет необходимость вступать в замену
подпрограммы после каждого совпадения символа, вместо этого захватывая все соседние символы
по мере необходимости.

Я не знаю синтаксис питонов для регулярных выражений на макушке,
ни все управляющие коды в Unicode, но результат будет выглядеть что-то
как это:

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

Наибольшее количество времени будет при копировании результатов в другую строку.
Наименьшее количество времени будет в поиске всех контрольных кодов, которые
было бы крошечным.

При прочих равных условиях регулярное выражение (как описано выше) - самый быстрый путь.

Другие вопросы по тегам