줄당 하나의 음수가 아닌 정수를 가진 파일 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 개의 문자를 찾는 것에 대해 걱정하는 것이 더 간단합니다.
- 다음 작업의 요점은 전체 파일을 lineno의 파일
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
-s
elimiter-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/F
5mil 라인을 넣고 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.txt
10000000-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