mik*_*iku 18 text-processing filter high-performance
给定每行一个非负整数的文件 L 和文本文件 F,仅保留 F 中那些行号出现在文件 L 中的行的快速方法是什么?
例子:
$ 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
Run Code Online (Sandbox Code Playgroud)
我正在寻找一个可以处理包含 5 亿个或更多条目的文件 L 的命令;文件 L 按数字排序。
注意:我正在实现 a 的一半,command-in-question但我只是想知道,是否也可以在这里使用一些 Unix 工具。
更新:感谢所有的答案,我今天学到了很多!我想接受更多一个答案,但这是不可能的。
mik*_*erv 11
grep -n | sort | sed | cut( export LC_ALL=C
grep -n '' | sort -t: -nmk1,1 ./L - |
sed /:/d\;n | cut -sd: -f2-
) <./F
Run Code Online (Sandbox Code Playgroud)
对于任何大小的输入,这应该可以很快工作(下面包括一些定时测试)。关于如何做的一些注意事项:
export LC_ALL=C
./F与./Llineno 的文件内联堆叠在一起,因此我们真正需要担心的唯一字符是 ASCII[0-9]数字和:冒号。grep -n ''
LINENO:插入到 stdin - 或 中每一行的开头<./F。sort -t: -nmk1,1 ./L -
sort完全忽略对其输入文件进行排序,而是(正确地)假定它们已-m预先-numerically排序并按排序顺序进行处理,基本上忽略了任何可能出现的-k1,1st-t:冒号字符之外的任何内容。sort将输出单个流,其中任何 lineno's in./L将立即位于./F. ./L的行总是排在最前面,因为它们较短。sed /:/d\;n
/:/冒号匹配,d则将其从输出中删除。否则,自动打印当前n行和ext 行。sed修剪不匹配冒号和下一行的顺序行对sort的输出- 或者,只修剪一行,然后是下一行。./Lcut -sd: -f2-
cut -s从输出中抑制那些不包含至少一个其-d:分隔符字符串的输入行- 因此./L's 的行被完全修剪。:冒号分隔的字段-f已经cut消失了 - 所有grep插入的 lineno 也是如此。seq 5 | sed -ne'2,3!w /tmp/L
s/.*/a-z &\& 0-9/p' >/tmp/F
Run Code Online (Sandbox Code Playgroud)
...生成 5 行样本输入。然后...
( export LC_ALL=C; </tmp/F \
grep -n '' | sort -t: -nmk1,1 ./L - |
sed /:/d\;n | cut -sd: -f2-
)| head - /tmp[FL]
Run Code Online (Sandbox Code Playgroud)
...印刷...
==> 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
Run Code Online (Sandbox Code Playgroud)
我创建了几个非常大的文件:
==> 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
Run Code Online (Sandbox Code Playgroud)
...将 500 万行/tmp/F和150 万行随机选择的行放入/tmp/L. 然后我做了:
seq 5000000 | tee /tmp/F |
sort -R | head -n1500000 |
sort -n >/tmp/L
Run Code Online (Sandbox Code Playgroud)
它打印:
time \
( export LC_ALL=C
grep -n '' | sort -t: -nmk1,1 ./L - |
sed /:/d\;n | cut -sd: -f2-
) <./F |wc - l
Run Code Online (Sandbox Code Playgroud)
(我在那里添加了反斜杠)
在当前提供的解决方案中,这是所有解决方案中最快的,但与上面在我的机器上生成的数据集相比。在其他人中,只有一个接近争夺第二名,那就是 meuh 在perl 这里。
这绝不是提供的原始解决方案 - 由于其他人提供的建议/灵感,它已经减少了三分之一的执行时间。请参阅帖子历史以了解较慢的解决方案(但为什么?)。
此外,值得注意的是,如果不是我系统的多 CPU 架构以及该管道中每个进程的并发执行,其他一些答案可能会更好地竞争。它们都同时工作——每个都在自己的处理器核心上——传递数据并完成整体的一小部分。它太酷了。
但这不是最快的解决方案。这里提供的最快的解决方案是C 程序。我叫它cselect。将其复制到我的 X 剪贴板后,我将其编译如下:
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
Run Code Online (Sandbox Code Playgroud)
然后我做了:
xsel -bo | cc -xc - -o cselect
Run Code Online (Sandbox Code Playgroud)
……结果是……
time \
./cselect /tmp/L /tmp/F |
wc -l
Run Code Online (Sandbox Code Playgroud)
Sté*_*las 10
我会使用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"
Run Code Online (Sandbox Code Playgroud)
我会用awk:
awk 'NR==FNR {a[$1]; next}; FNR in a' L.txt F.txt
Run Code Online (Sandbox Code Playgroud)
更新:我已经完成了性能测量;似乎这个版本在处理非常大的数据集时甚至可以更好地扩展(正如所述要求的情况),因为比较非常快并且过度补偿了构建哈希表所需的工作。
随着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;
}
}
Run Code Online (Sandbox Code Playgroud)