파일 A의 각 줄에 대해 파일 B의 일치하는 모든 줄을 패턴으로 바꿉니다.

파일 A의 각 줄에 대해 파일 B의 일치하는 모든 줄을 패턴으로 바꿉니다.

fileA에는 약 100,000개의 문자열이 포함되어 있습니다(사람 이름만 해당 a-zA-Z).

fileB에는 약 1억 줄이 있습니다.

프로그램

프로그램은 두 가지뿐입니다.

  • 문자열을 단일 점으로 교체
  • 문자열을 같은 길이의 점으로 교체

연산

for each lineB in fileB do
   for each lineA in fileA do
      if lineA matches with lineB; then
         replace the match in lineB with dots
         append the modified lineB' to file "res-length" or "res-single", depending on the program
      fi
   done
done

직접적인 솔루션은 매우 느립니다.

일치 항목은 대소문자를 구분해야 합니다.

다른 Linux 애플리케이션(gawk 등)도 추가로 설치할 수 있습니다.

$ cat fileA
agnes
Ari
Vika

$ cat fileB
12vika1991
ariagnes#!
ari45
lera56er

출력은 각 프로그램당 하나씩, 두 개의 파일이어야 합니다.

$ cat res-single  # replace a string with a single dot
12.1991
.agnes#!
ari.#!
.45

$ cat res-length  # replace a string with dots of the same length
12...1991
...agnes#!
ari.....#!
...45

이 작업의 단순화된 버전에는 고유한 출력이 필요합니다.첫 번째성냥. 따라서 프로그램 #2 대신 ...agnes#!출력 ari.....#!만으로 충분합니다.ari.....#!

단순화된 작업 알고리즘

for each lineB in fileB do
   find the first lineA in fileA that matches lineB
   if lineA is found; then
      replace the match in lineB with dots
      append the modified lineB' to file "res-length" or "res-single", depending on the program
   fi
done

파이썬 구현

def create_masks(wordlist=WordListDefault.TOP1M.path, replace_char='.'):
    # fileA lowercase
    names = PATTERNS_PATH.read_text().splitlines()

    masks_length = []
    masks_single = []
    with codecs.open(wordlist, 'r', encoding='utf-8', errors='ignore') as infile:
        for line in infile:
            line_lower = line.lower()
            for name in names:
                i = line_lower.find(name)
                if i != -1:
                    ml = f"{line[:i]}{replace_char * len(name)}{line[i + len(name):]}"
                    ms = f"{line[:i]}{replace_char}{line[i + len(name):]}"
                    masks_length.append(ml)
                    masks_single.append(ms)

    with open(MASKS_LENGTH, 'w') as f:
        f.writelines(masks_length)
    with open(MASKS_SINGLE, 'w') as f:
        f.writelines(masks_single)


if __name__ == '__main__':
    create_masks()

160만 개의 파일 A와 1,000개의 파일 B의 경우 약 3분 정도 걸렸다가 단 10초로 단축되었습니다 grep -iF -f fileA fileB > fileB.filtered.

@Ned64님 말씀이 맞습니다. 가장 빠른 방법은 간단한 C입니다. 이는 이 포럼의 주제가 아닙니다.

현재 Python 구현에서는 fileB의 2B 라인과 fileA의 35k 문자열을 처리하는 데 52일이 걸립니다. 순수 C가 한 시간 안에 이 작업을 수행할 수 있는지 더 이상 확신할 수 없습니다. CUDA가 실행 가능한 접근 방식인지 궁금합니다.

답변1

$ cat tst.awk
BEGIN {
    dots = sprintf("%*s",1000,"")
    gsub(/ /,".",dots)
    resSingle = "res-single"
    resLength = "res-length"
}
{ lc = tolower($0) }
NR==FNR {
    lgth = length($0)
    str2lgth[lc] = lgth
    str2dots[lc] = substr(dots,1,lgth)
    next
}
{
    for (str in str2lgth) {
        if ( s=index(lc,str) ) {
            bef = substr($0,1,s-1)
            aft = substr($0,s+str2lgth[str])
            print bef "." aft > resSingle
            print bef str2dots[str] aft > resLength
        }
    }
}

.

$ awk -f tst.awk fileA fileB

$ cat res-single
12.1991
ari.#!
.agnes#!
.45

$ cat res-length
12....1991
ari.....#!
...agnes#!
...45

위의 내용은 fileA에 1000자를 초과하는 줄이 없다고 가정합니다. 이것이 잘못된 경우 더 큰 숫자를 선택하거나 필요한 경우 코드를 추가하여 계산할 수 있습니다. 또한 fileA의 행이 fileB에서 발견되는 순서에 관심이 없고 정규식 비교 대신 문자열 비교를 수행하기를 원한다고 가정합니다. 둘 다 원하는 것이 아닐 경우 사소한 조정입니다.


아래 의견에 대한 응답으로 편집하십시오. fileA에서 줄의 최대 길이를 정적으로 정의할 수 없는 경우(100,000자도 안 됩니까?) 위의 내용을 수정하여 최대값을 계산해야 하고 fileA의 줄을 수정하는 방법은 다음과 같습니다. 모두 소문자입니다:

NR==FNR {
    lgth = length($0)
    str2lgth[$0] = lgth
    maxLgth = (lgth > maxLgth ? lgth : maxLgth)
    next
}
FNR==1 {
    dots = sprintf("%*s",maxLgth,"")
    gsub(/ /,".",dots)
    for ( str in str2lgth ) {
        str2dots[str] = substr(dots,1,str2lgth[str])
    }
    resSingle = "res-single"
    resLength = "res-length"
}
{
    lc = tolower($0)
    for (str in str2lgth) {
        if ( s=index(lc,str) ) {
            bef = substr($0,1,s-1)
            aft = substr($0,s+str2lgth[str])
            print bef "." aft > resSingle
            print bef str2dots[str] aft > resLength
        }
    }
}

답변2

여기에서는 간단한 Perl 기반 접근 방식을 사용할 수 있습니다.

방법:

키가 fileA의 소문자 라인(줄 바꿈 없음)이고 값이 동등한 포인트인 해시 %h를 채웁니다.

그런 다음 fileB의 각 행에 대해 해시 %h의 키가 대소문자를 구분하지 않고 존재하는지 테스트합니다. 그렇다면 사전 일치, 일치 및 사후 일치 데이터를 res-single 및 res-length 파일로 인쇄합니다. 첫 번째 일치 항목만 원하는 경우 "마지막" 문의 주석을 해제하세요.

$ perl -Mautodie -lne '
    BEGIN {
     open *{"FH$_"}, ">", qw[res-single res-length][$_] for 0..1;
     do{
       local @ARGV = pop;
       $h{do{chomp;lc;}} = s/././gr =~ tr/\n//dr while <>;
       @h = keys %h;
      };
    }
    for my $h ( @h ) {
      if ( /\Q$h/pi ) {
        my($p, $q) = (${^PREMATCH}, ${^POSTMATCH});
        print {*{"FH$_"}} $p, (".", $h{$h})[$_], $q for 0..1;
        #last;
      }
    }
' fileB fileA

$ more res-*

::::::::::::::
res-length
::::::::::::::
12....1991
ari.....#!
...agnes#!
...45

::::::::::::::
res-single
::::::::::::::
12.1991
ari.#!
.agnes#!
.45

답변3

최적화된 C 솔루션https://github.com/dizcza/people-names-as-passwords/blob/master/src/create_masks.c

나는 trie 데이터 구조를 사용했고 이를 통해 12분 안에 2B 행 fileB과 43,000행을 구문 분석할 수 있었습니다!fileA

귀하의 의견에 감사드립니다.

관련 정보