5

Вопрос разбивается на несколько похожих, дабы не создавать каждый раз отдельно. Используемые символы в строках: все цифры, все строчные и заглавные буквы английского алфавита. Строки вида:

a22tbf645
92STbfF4W
92rtRe7Ev
gyue73Pr4
u8t9D03gE
a2t4TA6Kk
Lj3D2Jrs1

желаемый ответ после применения одновременно всего указанного ниже: Lj3D2Jrs1

1.a Удалить строки, содержащие на любых позициях три или более любые разные цифры, идущие подряд.

1.b Удалить строки, содержащие на любых позициях три или более любые строчные буквы, идущие подряд.

1.с Удалить строки, содержащие на любых позициях три или более любые заглавные буквы, идущие подряд.

2.a Удалить строки, содержащие на любых позициях четыре или более любые цифры вообще в строке.

2.b Удалить строки, содержащие на любых позициях четыре или более любые строчные буквы вообще в строке.

2.c Удалить строки, содержащие четыре или более любые заглавные буквы вообще в строке.

  1. Удалить строки, содержащие на любых позициях одинаковые буквы подряд в разных регистрах, например aA Aa

Можно ли объединить все эти условия в одну команду? Предпочтительно sed, awk, grep, tr, cut, perl, python, может что-то еще - упор на скорость обработки- большие массивы.(чем кстати побыстрее?) спасибо

TWOfish
  • 617
  • Почему на этапе генерации строк эти условия не добавить? – Mart Aug 13 '18 at 18:11
  • Готового такого генератора, который бы смог все эти условия выполнить, нет. (К ним так же добавляется еще ряд других условий генерации, которые просто выходят за рамки вопроса)Сам сваять не в состоянии. Проще запустить генератор и отсеять ненужное. – TWOfish Aug 13 '18 at 18:14
  • Все эти условия можно использовать в несложной однопроходной машине состояний на быстром языке общего назначения - например, на С. – MBo Aug 13 '18 at 18:35
  • @Bl0wfish Гляньте этот вопрос, там автор интересуется выводом только уникальных строк без единого повтора, есть ответ с гибким кодом на питоне.https://ru.stackoverflow.com/questions/867163 Ан это вы есть... – Hellseher Aug 13 '18 at 22:16
  • На мой взгляд это разные задачи – TWOfish Aug 13 '18 at 22:22
  • @Bl0wfish Нашел интересный ответ в английской версии - https://stackoverflow.com/questions/34443946 – Hellseher Aug 13 '18 at 22:26
  • @Bl0wfish 3 пункт интересный, не могу его реализовать пока. – Hellseher Aug 13 '18 at 23:30
  • Кстати, для указанных вводных данных есть простой ответ - удалить все строки, т.к. строк длиной 10, содержащих только символы этих трёх подмножеств, и подпадающих под пункты 2, не существует. Может быть, реальные строки не такие? – MBo Aug 14 '18 at 04:43
  • @MBo Вы правы, это просто неудачный пример )), поправил. – TWOfish Aug 14 '18 at 08:02
  • @Hellseher п.3 - пока мне пришла в голову только одна операция - привести к одному регистру, проверка на дубль- если дубль -то в корзину, если нет - вернуть все назад. Фильтрацию на дубль одного регистра я все равно тоже делаю, просто тут опустил. – TWOfish Aug 14 '18 at 08:09
  • В общем, с использованием машины состояний десять миллионов строк из 9 символов (файл 110 мегабайт) обрабатываются за одну секунду, если не считать время на ввод-вывод (чтение файла с диска и запись это ещё 3-4 сек.) – MBo Aug 14 '18 at 09:17
  • @MBo на чем написали? – Hellseher Aug 14 '18 at 09:19
  • @Hellseher На Delphi под Windows (т.е. язык компилируемый) – MBo Aug 14 '18 at 09:24
  • @Hellseher Переписал втупую на Python, получается 41 сек (правда, я наверняка неоптимально всё делаю) – MBo Aug 14 '18 at 09:59
  • @MBo, я правильно понял, что Вы обрабатывали готовый семпл 110Mb? а не изначально генерировали с этими условиями сразу? В любом случае, мне видимо стоит обратиться к Вам попозже возмездно, для начала хочу почитать про машину состояний. Прошу учесть, что я впервые про такое от Вас услышал ), ибо я чайник, - просто хочу спросить - а ваш алгоритм можно запустить параллельно сразу на нескольких ядрах чтобы еще ускорить процесс? или это уже примерный максимум? – TWOfish Aug 14 '18 at 11:27
  • Я сгенерировал файл из указанного количества строк, потом загружал его. Запустить на нескольких ядрах можно, обработка одной строки от другой не зависит, только потребуется дополнительная работа по разделению на части, а после обработки - слиянию данных. Однако на чистом Python получается не так уж и быстро. Я правильно понял, что на выход должны проходить только те строки, которые удовлетворяют всем условиям одновременно? – MBo Aug 14 '18 at 12:12
  • @MBo да, правильно. Предложенный python я еще не пробовал, немного попозже испробую все варианты в боевых условиях на своем семпле 355Gb. Пока попробовал только egrep с регулярными выражениями - работает, но не так быстро как хотелось бы.. – TWOfish Aug 14 '18 at 12:28

3 Answers3

5

Вот (вероятно) малоэффективный и откровенно тупой, но рабочий вариант на классических регулярных выражениях:

grep -v\
 -e '[[:digit:]]\{3,\}'\
 -e '[[:lower:]]\{3,\}'\
 -e '[[:upper:]]\{3,\}'\
 -e '\([[:digit:]].*\)\{4,\}'\
 -e '\([[:lower:]].*\)\{4,\}'\
 -e '\([[:upper:]].*\)\{4,\}'\
 $(for i in {a..z}; do echo "-e $i${i^^} -e ${i^^}$i"; done)
Ainar-G
  • 16,042
  • Работает, сенкс. Последний фильтр правда сильно снижает скорость, раза в 3-4 от всех предыдущих вместе взятых. Но работает. – TWOfish Aug 14 '18 at 12:29
2

Решение с использованием простой машины состояний (детерминированного конечного автомата) было написано "в лоб" на Delphi, затем я его, как сумел, почти напрямую перевёл на Python, так что код громоздкий, явно не pythonic и производительность может страдать оттого, что я не использую какие-то встроенные штучки и не особо знаю, что влияет на производительность Python. Например, время сократилось с 41 до 33 секунд, когда я заменил выделение символа

        for i in range(len(line)):
            ch = line[i]

на

        for ch in line:

Для файла из 10 миллионов строк по 9 случайных символов (110 мегабайт), принадлежащих указанным множествам, нативный (Delphi) код под Windows на Celeron 2.8 ГГц затрачивает около 4-5 секунд (3-4 секунды на загрузку данных с HDD и около секунды на саму обработку). Данный код (Python 3.6) тратит на всё 33 секунды (из них 11 сек. на загрузку файла, если просто прочитать все строки и ничего не делать).

Код проходит по каждой строке, считая количество больших, малых букв и цифр и прерывает обработку строки, если какой-либо из счётчиков превышает предел (3) (условия 2).

state - это текущее состояние, обозначает, какого типа символ обрабатывался ранее. 0 - неопределённо, 1 - цифра, 2 - заглавная, 3 - маленькая буква.

Если state не меняется, то проверяется длина серии seriescount из символов текущего типа (условия 1)

Если state меняется с 2 на 3 или наоборот, то проверяется, не был ли прошлый символ копией текущего в другом регистре (условие 3).

def dellines():
    infile = open("d:\m1.txt", "r")
    outfile = open("d:\m2.txt", "w")

    for line in infile:
        seriescnt = 0
        bigcnt = 0
        smallcnt = 0
        digitcnt = 0
        state = 0
        for ch in line:

            if (ch >= "0") and (ch <= "9"):

                if (state == 1):
                    seriescnt += 1
                    if (seriescnt > 2):
                        state = 0
                        break
                else:
                  seriescnt = 1

                digitcnt += 1
                if (digitcnt > 3):
                     state = 0
                     break
                state = 1
            elif (ch >= "A") and (ch <= "Z"):

                if (state == 3):
                    if (ord(lastch) == ord(ch) + 32):
                        state = 0
                        break

                if (state == 2):
                    seriescnt += 1
                    if (seriescnt > 2):
                        state = 0
                        break
                else:
                  seriescnt = 1

                bigcnt += 1
                if (bigcnt > 3):
                     state = 0
                     break

                lastch = ch
                state = 2
            elif (ch >= "a") and (ch <= "z"):

                if (state == 2):
                    if (ord(lastch) == ord(ch) - 32):
                        state = 0
                        break

                if (state == 3):
                    seriescnt += 1
                    if (seriescnt > 2):
                        state = 0
                        break
                else:
                  seriescnt = 1

                smallcnt += 1
                if (smallcnt > 3):
                     state = 0
                     break

                lastch = ch
                state = 3

        if (state):
            outfile.write(line)
    outfile.close()
    infile.close()

start = time.time()
dellines()
end = time.time()
print(end - start)
MBo
  • 53,555
  • библиотеки какие импортировать надо? это python3 ? файлы входа-выхода задаются флагами -r -w ? а если в трубу надо воткнуть? – TWOfish Aug 14 '18 at 13:44
  • Я ничего не импортировал, кроме time для замера времени. Да, Python 3.6. Флаг "r" открывает существующий файл на чтение, "w" создаёт новый для записи. C трубами import sys и for line in sys.stdin: и вывод sys.stdout.write(line) – MBo Aug 14 '18 at 13:53
  • 1
    Работает и с Python 2.7: https://ideone.com/FFZHv0 – MBo Aug 14 '18 at 16:30
  • да! спасибо. Я уже зарядил первый вариант с большим файлом, хочу сравнить по времени с тем же grep – TWOfish Aug 14 '18 at 16:41
  • замерял в трубе с помощью pv - скорость примерно в 5 раз выше, чем у предложенного варианта ниже с регулярными выражениями grep – TWOfish Aug 14 '18 at 17:11
  • А какие абсолютные времена получаются? – MBo Aug 14 '18 at 17:24
  • я бы не стал тут всерьез тестировать скорости, так как у меня работает на гостевой виртуальной ubuntu VMWare из-под Windows хоста (то есть в принципе могу и на delphi запускать обработку я так понимаю), плюс большие файлы читаются и сохраняются в общие расшаренные папки на HDD, что, как я думаю, тоже снижает скорость. Плюс на ядро ложится и другая нагрузка. У меня Intel(R) Core(TM) i5-3570K CPU @ 3.40GHz. Сообщу общий time когда файл обработается. Утилита pv в трубе уже после работы Вашего скрипта перед сохранением в файл пишет скорость в среднем примерно около 300k/s – TWOfish Aug 14 '18 at 18:43
1

Версия на Python за основу взяты ответы из СО.

Создаем для примера файл из случайной выборки 100М с заданным патерном из вопроса: [a-zA-Z0-9]{9}

Характеристики машины:

~$ uname -r; grep -im1 "model name" /proc/cpuinfo
4.17.3-200.fc28.x86_64
model name      : Intel(R) Core(TM) i7-3770S CPU @ 3.10GHz

Исправленный вариант только на регулярных выражениях без выполнения условия 3

~$ cat lines_filter_re.py

#!/usr/bin/env python3
import sys
import re
import fileinput


for line in fileinput.input():
    pat_re = re.compile(r""""
       ([0-9]){3}            # Any 3 sequensial numbers
       |([a-z]){3}           # Any 3 lsequensia lower case letters
       |([A-Z]){3}           # Any 3 sequensia lupper case letters
       |(.*[0-9].*){4}       # any 4 digits in any place
       |(.*[A-Z].*){4}       # any 4 upper case ltters at any place
       |(.*[a-z].*){4}       # any 4 loser case lttters at any place
       """, re.VERBOSE)

    if not pat_re.findall(line):
        sys.stdout.write(line)
# end of script

~$ cat test.txt; echo; ./lines_filter_re.py < test.txt
a22tbf645
92STbfF4W
92rtRe7Ev
gyue73Pr4
u8t9D03gE
a2t4TA6Kk
aAb12cAB1
Lj3D2Jrs1
---------
a2t4TA6Kk
aAb12cAB1
Lj3D2Jrs1

~$ wc -l file_9w_100M
6180836 file_9w_100M

~$ time ./lines_filter_re.py >/dev/null < file_9w_100M
real    1m10.668s
user    1m10.424s
sys     0m0.060s

Метод на Rust, работает x15 раз быстрей:

extern crate regex;

use regex::RegexSet;
use std::io::{stdin, BufRead};

fn main() {
    let set = RegexSet::new(&[
        r"([0-9]){3}",
        r"([a-z]){3}",
        r"([A-Z]){3}",
        r"(.*[0-9].*){4}",
        r"(.*[A-Z].*){4}",
        r"(.*[a-z].*){4}",
    ]).unwrap();

    let stdin = stdin();
    let test_string = "Test me once";

    println!("{}", set.is_match(test_string));

    for line in stdin.lock().lines() {
        let mut line_str = line.unwrap();

        if set.is_match(&mut line_str) {
            continue;
        } else {
            println!("{}", line_str);
        }
    }
}
// End of main.rs

Тесты

~$ time ./lfr  < ../../../../Python/test.txt
true
a2t4TA6Kk
aAb12cAB1
Lj3D2Jrs1
real    0m0.004s
user    0m0.002s
sys     0m0.002s

~$ time ./lfr >/dev/null < ../../../../Python/file_9w_100M
real    0m2.934s
user    0m2.866s
sys     0m0.062s

Ссылки

Hellseher
  • 3,622
  • 12
  • 33
  • Мммм.. у меня ваш скрипт работает не корректно: echo 11d34433RReeAeTt | hellseher.py - ответ 11d34433RReeAeTt фильтрует только три одинаковых символа причем подряд – TWOfish Aug 14 '18 at 15:48
  • @Bl0wfish добавте в вопрос проверочные строки на входе и желаемые результат на выходе. – Hellseher Aug 14 '18 at 16:00
  • @Bl0wfish Переписал, даже узнал новое в Питоне. Условие 3 требуют сложной регулярки но прогоняет 6м строк за 1мин. – Hellseher Aug 14 '18 at 22:10
  • спасибо Вам за старания, он работает, только вот без условия п.3. Хотел устроить соревнование между вашим скриптом и скриптом @Mbo, но на поверку оказалось длиннее, чем я ожидал с обработкой файла в 355GB, тот со вчерашнего дня все еще обрабатывается.. – TWOfish Aug 15 '18 at 13:29
  • @Bl0wfish без метки, не видны сообщения. Решаю вопрос о третьем условии https://ru.stackoverflow.com/questions/868744 и переписываю все на Rust должно дать прирост в скорости 300-400% – Hellseher Aug 15 '18 at 21:11