Ответ 1
Коммандная команда делает это.
У меня есть два файла A
- nodes_to_delete
и B
- nodes_to_keep
. Каждый файл имеет много строк с числовыми идентификаторами.
Я хочу иметь список числовых идентификаторов, которые находятся в nodes_to_delete
но НЕ в nodes_to_keep
, например, .
Делать это в базе данных PostgreSQL неоправданно медленно. Любой аккуратный способ сделать это в Bash с помощью инструментов Linux CLI?
ОБНОВЛЕНИЕ: Это, кажется, работа Pythonic, но файлы действительно, действительно большие. Я решил некоторые подобные проблемы, используя uniq
, sort
и некоторые методы теории множеств. Это было примерно на два-три порядка быстрее, чем эквиваленты базы данных.
Коммандная команда делает это.
Кто-то показал мне, как сделать это в пару месяцев назад, а потом я не мог найти его какое-то время... и, глядя, я наткнулся на ваш вопрос. Вот он:
set_union () {
sort $1 $2 | uniq
}
set_difference () {
sort $1 $2 $2 | uniq -u
}
set_symmetric_difference() {
sort $1 $2 | uniq -u
}
Используйте 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 столбца с таким макетом:
lines_unique_to_FILE1
lines_unique_to_FILE2
lines_which_appear_in_both
Используя приведенные выше примеры файлов, запустите comm без аргументов. Обратите внимание на три столбца.
$ comm <(sort keepNodes) <(sort deleteNodes)
amber
ann
bob
Подавить столбец 1, 2 или 3 с помощью -N; обратите внимание, что когда столбец скрыт, пробел уменьшается.
$ 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, я могу в значительной степени поспорить, что вы не найдете более быстрый способ сделать это, используя плоские файлы. Вы должны иметь возможность сделать простое внутреннее соединение и предположить, что оба идентификатора cols индексируются, которые должны быть очень быстрыми.
Таким образом, это немного отличается от других ответов. Я не могу сказать, что компилятор C++ является в точности "инструментом CLI для Linux", но работает с 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 BA
(не AB
, так как B
- nodes_to_keep
), и полученная разница будет напечатана в stdout.
Обратите внимание, что я упустил несколько рекомендаций C++, чтобы сделать код проще.
Можно было бы сделать много дополнительных оптимизаций скорости (за счет увеличения памяти). mmap
также будет особенно полезен для больших наборов данных, но это сделает код гораздо более сложным.
Поскольку вы упомянули, что наборы данных велики, я подумал, что одновременное чтение по nodes_to_delete
строки может быть хорошей идеей для уменьшения потребления памяти. Подход, использованный в приведенном выше коде, не особенно эффективен, если в вашем 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