줄 번호로 파일 필터링 파일 L과 텍스트 파일

줄당 하나의 음수가 아닌 정수를 가진 파일 L과 텍스트 파일 F가 주어지면 줄 번호가 파일 L에 나타나는 F의 줄만 유지하는 가장 빠른 방법은 무엇입니까?

예:

$ cat L.txt
1
3

$ cat F.txt
Hello World
Hallo Welt
Hola mundo

$ command-in-question -x L.txt F.txt
Hello World
Hola mundo

5 억 개 이상의 항목이있는 파일 L을 처리 할 수있는 명령을 찾고 있습니다. 파일 L은 숫자로 정렬됩니다.

참고 : 구현을 반쯤 진행 command-in-question했지만 여기에서 유닉스 도구를 사용할 수 있는지 궁금합니다.


업데이트 : 모든 답변에 감사드립니다. 오늘 많은 것을 배웠습니다! 하나 이상의 답변을 수락하고 싶지만 불가능합니다.



답변

C의미있는 오류 메시지를 생략 하면 :

#include <stdio.h>
#include <stdlib.h>

int main (int argc, char *argv[]) {

    FILE *L;
    FILE *F;

    unsigned int to_print;
    unsigned int current = 0;
    char *line = NULL;
    size_t len = 0;

    if ((L = fopen(argv[1], "r")) == NULL) {
        return 1;
    } else if ((F = fopen(argv[2], "r")) == NULL) {
        fclose(L);
        return 1;
    } else {

        while (fscanf(L, "%u", &to_print) > 0) {
            while (getline(&line, &len, F) != -1 && ++current != to_print);
            if (current == to_print) {
                printf("%s", line);
            }
        }

        free(line);
        fclose(L);
        fclose(F);
        return 0;
    }
}


답변

사용 awk하지만 L.txt메모리 의 전체 내용을 저장하지 않고 불필요한 해시 조회를 수행합니다. ;-).

list=L.txt file=F.txt
LIST="$list" awk '
  function nextline() {
    if ((getline n < list) <=0) exit
  }
  BEGIN{
    list = ENVIRON["LIST"]
    nextline()
  }
  NR == n {
    print
    nextline()
  }' < "$file"


답변

grep -n | sort | sed | cut

(   export LC_ALL=C
    grep -n ''   | sort -t:  -nmk1,1 ./L - |
    sed /:/d\;n  | cut  -sd: -f2-
)   <./F

그것은 모든 크기의 입력으로 꽤 빨리 작동합니다 (일부 시간 테스트가 아래에 포함되어 있음) . 방법에 대한 몇 가지 참고 사항 :

  • export LC_ALL=C
    • 다음 작업의 요점은 전체 파일을 lineno의 파일 ./F과 인라인으로 쌓는 ./L것이므로 ASCII [0-9]문자와 :콜론 만 걱정할 필요가 있습니다 .
    • 따라서 UTF-8이 포함 된 경우보다 128 개 문자 집합에서 11 개의 문자를 찾는 것에 대해 걱정하는 것이 더 간단합니다.
  • grep -n ''
    • 그러면 LINENO:stdin-또는에서 모든 줄의 머리글에 문자열이 삽입됩니다 <./F.
  • sort -t: -nmk1,1 ./L -
    • sort무시 전혀 입력 파일을 정렬하고 대신합니다 (제대로) 가 미리 정렬되어 가정 및 -m그들을 erges -numerically기본적으로 가능한 모든 이외에는 아무 것도 무시하고, 정렬 된 순서 -k1,1일 발생 -t:어쨌든 콜론 문자를.
    • 이것은 (일부 시퀀스가 ​​얼마나 멀리 떨어져 있는지에 따라) 수행 할 임시 공간이 필요할 수 있지만 적절한 정렬과 비교할 때 많이 필요하지 않으며 백 트랙킹이 없기 때문에 매우 빠릅니다.
    • sort./L라인 번호가의 해당 라인 바로 앞에 오는 단일 스트림을 출력합니다 ./F. ./L줄이 항상 짧기 때문에 항상 줄이 먼저 나옵니다.
  • sed /:/d\;n
    • 현재 행이 /:/콜론 과 일치하면 d출력에서 제외됩니다. 그렇지 않으면 현재 및 n내선을 자동 인쇄합니다 .
    • 그리고 sed자두 sort에의 출력 만을 콜론과 다음 줄을 일치하지 않는 연속 라인 쌍 – 또는에서 불과 라인에 ./L다음 다음.
  • cut -sd: -f2-
    • cut -selimiter -d:문자열 중 하나 이상을 포함하지 않는 입력 행의 출력을 억제합니다. 따라서 ./L행이 완전히 제거됩니다.
    • 그런 줄에 대해서는 첫 번째 :콜론으로 구분 된 -f필드가 cut사라지고 grep삽입 된 모든 lineno 가 사라집니다 .

작은 입력 테스트

seq 5 | sed -ne'2,3!w /tmp/L
        s/.*/a-z &\& 0-9/p' >/tmp/F

… 5 줄의 샘플 입력을 생성합니다. 그때…

(   export LC_ALL=C; </tmp/F \
    grep -n ''   | sort -t:  -nmk1,1 ./L - |
    sed /:/d\;n  | cut  -sd: -f2-
)|  head - /tmp[FL]

…인쇄물…

==> standard input <==
a-z 1& 0-9
a-z 4& 0-9
a-z 5& 0-9

==> /tmp/F <==
a-z 1& 0-9
a-z 2& 0-9
a-z 3& 0-9
a-z 4& 0-9
a-z 5& 0-9

==> /tmp/L <==
1
4
5

더 큰 시한 테스트

꽤 큰 파일 몇 개를 만들었습니다.

seq 5000000 | tee /tmp/F |
sort -R | head -n1500000 |
sort -n >/tmp/L

/tmp/F5mil 라인을 넣고 1.5mil을 임의로 선택한 라인에 넣습니다 /tmp/L. 나는 그랬다.

time \
(   export LC_ALL=C
    grep -n ''   | sort -t:  -nmk1,1 ./L - |
    sed /:/d\;n  | cut  -sd: -f2-
)   <./F |wc - l

인쇄 :

1500000
grep -n '' \
    0.82s user 0.05s system 73% cpu 1.185 total
sort -t: -nmk1,1 /tmp/L - \
    0.92s user 0.11s system 86% cpu 1.185 total
sed /:/d\;n \
    1.02s user 0.14s system 98% cpu 1.185 total
cut -sd: -f2- \
    0.79s user 0.17s system 80% cpu 1.184 total
wc -l \
    0.05s user 0.07s system 10% cpu 1.183 total

(백 슬래시를 추가했습니다)

현재 여기에 제공되는 솔루션 중에서 이것은 가장 빠르지 만 내 컴퓨터에서 위에서 생성 된 데이터 세트에 대해 피트했을 때 하나입니다. 다른 사람들 중 단 한 사람 만이 2 위를 차지하기 시작 perl 했습니다 .

이것은 원래 제공된 솔루션이 결코 아닙니다. 다른 사람들이 제공하는 조언 / 영감 덕분에 실행 시간의 3 분의 1이 줄었습니다. 더 느린 솔루션에 대해서는 포스트 히스토리를 참조하십시오 (그러나 그 이유는 무엇입니까?) .

또한 내 시스템의 다중 CPU 아키텍처가 아니고 해당 파이프 라인의 각 프로세스를 동시에 실행하지 않으면 다른 답변이 훨씬 더 잘 경쟁 할 수 있습니다. 이들은 모두 동시에 자체 프로세서 코어에서 데이터를 전달하고 전체의 작은 부분을 수행하면서 동시에 작동합니다. 꽤 멋지다.

그러나 가장 빠른 해결책은 …

그러나 가장 빠른 솔루션은 아닙니다. 여기서 제공되는 가장 빠른 솔루션은 C 프로그램 입니다. 나는 그것을 불렀다 cselect. X 클립 보드에 복사 한 후 다음과 같이 컴파일했습니다.

xsel -bo | cc -xc - -o cselect

나는 그랬다.

time \
    ./cselect /tmp/L /tmp/F |
wc -l

… 결과는 …

1500000
./cselect /tmp/L /tmp/F  \
    0.50s user 0.05s system 99% cpu 0.551 total
wc -l \
    0.05s user 0.05s system 19% cpu 0.551 total


답변

나는 사용할 것이다 awk:

awk 'NR==FNR {a[$1]; next}; FNR in a' L.txt F.txt

업데이트 : 성능 측정을 마쳤습니다. 비교가 매우 빠르며 해시 테이블을 작성하는 데 필요한 노력을 과도하게 보상하기 때문에이 버전은 매우 큰 데이터 세트 (규정 된 요구 사항의 경우와 같이)를 사용하여 확장하는 것이 더 좋습니다.


답변

완전성을 위해 : 우리는 Stéphane Chazelas의 대답에 우수한 awk 스크립트와 kos의 대답에있는 perl 스크립트를 병합 할 수 있지만 perl이 awk보다 빠를 수 있기를 바랍니다. (원래 질문과 일치하도록 인수 순서를 변경했습니다).

#!/usr/bin/env perl
use strict;

die "Usage: $0 l f\n" if $#ARGV+1 != 2;
open(L,$ARGV[0]) or die "$ARGV[0]: $!";
open(F,$ARGV[1]) or die "$ARGV[1]: $!";

while(my $number = <L>){
    #chop $number;
    while (<F>) {
        if($. == $number){
            print;
            last;
        }
    }
}


답변

간단한 Perl 스크립트를 작성했습니다.

Usage: script.pl inputfile_f inputfile_f

#!/usr/bin/env perl

$number_arguments = $#ARGV + 1;
if ($number_arguments != 2) {
    die "Usage: script.pl inputfile_f inputfile_l\n";
}

open($f, '<', $ARGV[0])
    or die "$ARGV[0]: Not found\n";
open($l, '<', $ARGV[1])
    or die "$ARGV[1]: Not found\n";

@line_numbers = <$l>;

while ($line = <$f>) {
    $count_f ++;
    if ($count_f == @line_numbers[$count_l]) {
        print $line;
        $count_l ++;
    }
}
  • 잔뜩 F.txt
  • 잔뜩 L.txt
  • 각 줄을 L.txt배열에 저장
  • F.txt현재 줄 번호와 현재 배열 색인을 추적하여 한 줄씩 읽 습니다. F.txt현재 줄 번호를 증가시킵니다 . 경우 생성 F.txt전류 선 번호가 현재 배열 인덱스에 어레이의 내용과 일치하는, 상기 전류 선을 인쇄하고 인덱스를 증가

비용 및 복잡성 고려 사항 :

할당 비용, 비교 비용 및 라인 인쇄 비용을 고려하여 N 1 을 라인 수로 F.txt, N 2 를 라인 수로 지정 L.txt하면 while루프는 최대 N 1 번 실행됩니다 . 2N 1 + N 2 할당 (명명하게 N 1 > N 2로 가정 ), 2N 1 비교 및 N 2 프린트로 이어짐; 각 작업의 비용이 동일 할 경우 while루프 를 실행하는 데 드는 총 비용 은 4N 1 + 2N 2 이므로 O (N) 스크립트가 복잡해집니다.

1000 만 줄의 입력 파일에서 테스트하십시오 .

10 만 라인을 사용하여 F.txt무작위로 50 자 길이의 라인을 포함하는 파일과 10 만 라인의 L.txt10000000-1에서 번호가 들어있는 파일 (최악의 시나리오) :

~/tmp$ for ((i=0; i<3; i++)); do time ./script.pl F.txt L.txt > output; done

real    0m15.628s
user    0m13.396s
sys 0m2.180s

real    0m16.001s
user    0m13.376s
sys 0m2.436s

real    0m16.153s
user    0m13.564s
sys 0m2.304s


답변

이 perl 솔루션은 다른 awk 또는 perl 솔루션보다 20 % 정도 빠르지 만 C의 솔루션만큼 빠르지는 않습니다.

perl -e '
  open L, shift or die $!;
  open F, shift or die $!;
  exit if ! ($n = <L>);
  while (1) {
    $_ = <F>;
    next if $. != $n;
    print;
    exit if ! ($n = <L>);
  }
' -- L F