我有两个文件
A
-nodes_to_delete
和B
-nodes_to_keep
。每个文件都有很多带有数字 ID 的行。
我想要在
nodes_to_delete
但不在nodes_to_keep
中的数字ID列表,即A\B
在 PostgreSQL 数据库中执行它非常慢。使用 Linux CLI 工具在 bash 中有什么巧妙的方法吗?
更新: 这似乎是一个 Pythonic 的工作,但文件真的非常大。我已经使用
uniq
、sort
和一些集合论技术解决了一些类似的问题。这比数据库等效项快两到三个数量级。
comm 命令就是这样做的。
使用
uniq -u
(--unique
),它只打印非重复行:
set_union () {
sort $1 $2 | uniq
}
set_difference () {
sort $1 $2 $2 | uniq -u
}
set_symmetric_difference() {
sort $1 $2 | uniq -u
}
使用
uniq -d
(--repeated
),它只打印重复的行:
set_intersection () {
sort $1 $2 | uniq -d
}
使用
comm
- 它将逐行比较两个排序的文件。
此命令将返回 deleteNodes 独有的行,而不是 keepNodes。
comm -1 -3 <(sort keepNodes) <(sort deleteNodes)
让我们创建名为
keepNodes
和 deleteNodes
的文件,并将它们用作 comm
命令的未排序输入。
$ cat > keepNodes <(echo bob; echo amber;)
$ cat > deleteNodes <(echo bob; echo ann;)
默认情况下,不带参数运行 comm 会打印 3 列布局:
$ comm FILE1 FILE2
lines_unique_to_FILE1
lines_unique_to_FILE2
lines_which_appear_in_both
使用上面的示例文件,运行不带参数的 comm。注意三列。
$ comm <(sort keepNodes) <(sort deleteNodes)
amber
ann
bob
用 -N 抑制第 1、2 或 3 列;请注意,当列被隐藏时,空白会缩小。
$ comm -1 <(sort keepNodes) <(sort deleteNodes)
ann
bob
$ comm -2 <(sort keepNodes) <(sort deleteNodes)
amber
bob
$ comm -3 <(sort keepNodes) <(sort deleteNodes)
amber
ann
$ comm -1 -3 <(sort keepNodes) <(sort deleteNodes)
ann
$ comm -2 -3 <(sort keepNodes) <(sort deleteNodes)
amber
$ comm -1 -2 <(sort keepNodes) <(sort deleteNodes)
bob
如果您在没有先对文件进行排序的情况下执行 comm,它会优雅地失败,并显示一条关于哪个文件未排序的消息。
comm: file 1 is not in sorted order
comm
是专门为这种用例设计的,但它需要排序输入。
awk
可以说是一个更好的工具,因为它可以相当直接地找到集合差异,不需要sort
,并提供额外的灵活性。
awk 'NR == FNR { a[$0]; next } !($0 in a)' nodes_to_keep nodes_to_delete
也许,例如,你只想找到代表非负数的线的差异:
awk -v r='^[0-9]+$' 'NR == FNR && $0 ~ r {
a[$0]
next
} $0 ~ r && !($0 in a)' nodes_to_keep nodes_to_delete
也许您需要一种更好的方法来在 postgres 中执行此操作,我敢打赌您不会找到使用平面文件来执行此操作的更快方法。你应该能够做一个简单的内部连接并假设两个 id 列都被索引应该非常快。
另一个可移植的解决方案,也适用于 multisets,一个允许元素的多个实例的集合,是在单独的文件中使用 grep 和模式:
grep -Fvx -f B A
参数:
如果 B 中的模式与 A 中的一行不匹配,则命令输出该行,否则不输出任何内容。
这个解决方案的一个很好的特点是它可以使用多列文件(对于
A
),而comm
和uniq -u
解决方案需要一个列文件。
所以,这与其他答案略有不同。我不能说 C++ 编译器完全是一个“Linux CLI 工具”,但是运行
g++ -O3 -march=native -o set_diff main.cpp
(在 main.cpp
中使用以下代码可以做到这一点):
#include<algorithm>
#include<iostream>
#include<iterator>
#include<fstream>
#include<string>
#include<unordered_set>
using namespace std;
int main(int argc, char** argv) {
ifstream keep_file(argv[1]), del_file(argv[2]);
unordered_multiset<string> init_lines{istream_iterator<string>(keep_file), istream_iterator<string>()};
string line;
while (getline(del_file, line)) {
init_lines.erase(line);
}
copy(init_lines.begin(),init_lines.end(), ostream_iterator<string>(cout, "\n"));
}
要使用,只需运行
set_diff B A
(notA B
,因为B
是nodes_to_keep
),结果差异将打印到标准输出。
请注意,我放弃了一些 C++ 最佳实践以保持代码更简单。
可以进行许多额外的速度优化(以更多内存为代价)。
mmap
对于大型数据集也特别有用,但这会使代码更加复杂。
既然你提到数据集很大,我认为一次读取
nodes_to_delete
一行可能是减少内存消耗的好主意。如果你的 nodes_to_delete
中有很多欺骗,上面代码中采用的方法并不是特别有效。此外,订单不会保留。
更容易复制和粘贴到
bash
中的东西(即跳过创建 main.cpp
):
g++ -O3 -march=native -xc++ -o set_diff - <<EOF
#include<algorithm>
#include<iostream>
#include<iterator>
#include<fstream>
#include<string>
#include<unordered_set>
using namespace std;
int main(int argc, char** argv) {
ifstream keep_file(argv[1]), del_file(argv[2]);
unordered_multiset<string> init_lines{istream_iterator<string>(keep_file), istream_iterator<string>()};
string line;
while (getline(del_file, line)) {
init_lines.erase(line);
}
copy(init_lines.begin(),init_lines.end(), ostream_iterator<string>(cout, "\n"));
}
EOF