Pergunta

Eu tenho dois arquivos A-nodes_to_delete e B-nodes_to_keep. Cada arquivo possui muitas linhas com IDs numéricos.

Eu quero ter a lista de IDs numéricos que estão em nodes_to_delete mas não em nodes_to_keep, por exemplo alt text .

Fazer isso dentro de um banco de dados PostgreSQL é irracionalmente lento. Alguma maneira interessante de fazer isso no Bash usando as ferramentas Linux CLI?

ATUALIZAR: Isso parece ser um trabalho pitônico, mas os arquivos são muito, muito grandes. Eu resolvi alguns problemas semelhantes usando uniq, sort e algumas técnicas de teoria de conjuntos. Isso foi cerca de duas ou três ordens de magnitude mais rápida que os equivalentes de banco de dados.

Foi útil?

Solução

o Comm O comando faz isso.

Outras dicas

Alguém me mostrou como fazer exatamente isso em Sh alguns meses atrás, e então eu não consegui encontrá -lo por um tempo ... e, enquanto olhava, tropecei em sua pergunta. Aqui está :

set_union () {
   sort $1 $2 | uniq
}

set_difference () {
   sort $1 $2 $2 | uniq -u
}

set_symmetric_difference() {
   sort $1 $2 | uniq -u
}

Usar comm - Ele comparará dois arquivos classificados linha por linha.

A resposta curta para sua pergunta

Este comando retornará linhas exclusivas dos deletenodos, mas não linhas em KeepNodes.

comm -1 -3 <(sort keepNodes) <(sort deleteNodes)

Exemplo de configuração

Vamos criar os arquivos nomeados keepNodes e deleteNodes, e use -os como entrada não classificada para o comm comando.

$ cat > keepNodes <(echo bob; echo amber;)
$ cat > deleteNodes <(echo bob; echo ann;)

Por padrão, executar o Comm sem argumentos imprime 3 colunas com este layout:

lines_unique_to_FILE1
    lines_unique_to_FILE2
        lines_which_appear_in_both

Usando nossos arquivos de exemplo acima, execute o comando sem argumentos. Observe as três colunas.

$ comm <(sort keepNodes) <(sort deleteNodes)
amber
    ann
        bob

Supressão de saída da coluna

Suprimir a coluna 1, 2 ou 3 com -n; Observe que quando uma coluna está oculta, o espaço em branco diminui.

$ 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

A classificação é importante!

Se você executar o Comm sem primeiro classificar o arquivo, ele falha graciosamente com uma mensagem sobre qual arquivo não foi classificado.

comm: file 1 is not in sorted order

comm foi projetado especificamente para esse tipo de caso de uso, mas requer entrada classificada.

awk é sem dúvida uma ferramenta melhor para isso, pois é bastante direto para encontrar diferença de conjunto, não requer sort, e oferece flexibilidade adicional.

awk 'NR == FNR { a[$0]; next } !($0 in a)' nodes_to_keep nodes_to_delete

Talvez, por exemplo, você gostaria de encontrar apenas a diferença nas linhas que representam números não negativos:

awk -v r='^[0-9]+$' 'NR == FNR && $0 ~ r {
    a[$0]
    next
} $0 ~ r && !($0 in a)' nodes_to_keep nodes_to_delete

Talvez você precise de uma maneira melhor de fazê -lo no Postgres, posso apostar que você não encontrará uma maneira mais rápida de fazê -lo usando arquivos planos. Você deve ser capaz de fazer uma junção interna simples e assumindo que os dois cols de ID sejam indexados que devem ser muito rápidos.

Então, isso é um pouco diferente das outras respostas. Não posso dizer que um compilador C ++ é exatamente uma "ferramenta Linux CLI", mas executando g++ -O3 -march=native -o set_diff main.cpp (com o código abaixo em main.cpp pode fazer o truque):

#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"));
}

Para usar, basta correr set_diff B A (não A B, desde B é nodes_to_keep) e a diferença resultante será impressa no stdout.

Observe que perdoo algumas práticas recomendadas do C ++ para manter o código mais simples.

Muitas otimizações de velocidade adicionais podem ser feitas (pelo preço de mais memória). mmap também seria particularmente útil para grandes conjuntos de dados, mas isso tornaria o código muito mais envolvido.

Desde que você mencionou que os conjuntos de dados são grandes, pensei que a leitura nodes_to_delete Uma linha de cada vez pode ser uma boa idéia para reduzir o consumo de memória. A abordagem adotada no código acima não é particularmente eficiente se houver muitas bobagens em seu nodes_to_delete. Além disso, a ordem não é preservada.


Algo mais fácil de copiar e colar em bash (ou seja, pulando a criação de 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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top