Question

J'ai deux fichiers A-nodes_to_delete et B-nodes_to_keep. Chaque fichier a un grand nombre de lignes avec Ids numériques.

Je veux avoir la liste des identifiants numériques qui sont nodes_to_delete mais pas dans nodes_to_keep, par exemple text alt .

Le faire au sein d'une base de données PostgreSQL est excessivement lent. Toute belle façon de le faire en utilisant des outils bash Linux CLI?

Mise à jour: Cela semble être un travail Pythonic, mais les fichiers sont vraiment, vraiment grand. J'ai résolu des problèmes similaires en utilisant uniq, sort et certaines techniques de théorie des ensembles. Il était environ deux ou trois ordres de grandeur plus rapide que les équivalents de base de données.

Était-ce utile?

La solution

La commande comm fait cela.

Autres conseils

Quelqu'un m'a montré comment faire exactement cela en sh il y a deux mois, et je ne pouvais pas trouver pendant un certain temps ... et en regardant je suis tombé sur votre question. Ici, il est:

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

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

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

Utilisez comm - il compare deux fichiers triés par ligne

.

La réponse courte à votre question

Cette commande renvoie des lignes uniques à deleteNodes, mais pas dans les lignes keepNodes.

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

Exemple de configuration

Créons les fichiers nommés keepNodes et deleteNodes, et les utiliser comme entrée pour la commande non triés de comm.

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

Par défaut, en cours d'exécution sans comm arguments imprime 3 colonnes avec cette mise en page:

lines_unique_to_FILE1
    lines_unique_to_FILE2
        lines_which_appear_in_both

En utilisant notre exemple fichiers ci-dessus, exécutez comm sans arguments. Notez les trois colonnes.

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

La suppression de sortie de colonne

Supprimer la colonne 1, 2 ou 3 avec -N; noter que lorsqu'une colonne est cachée, les espaces blancs se rétrécit vers le haut.

$ 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

Le tri est important!

Si vous exécutez comm sans d'abord trier le fichier, il échoue avec élégance avec un message sur lequel le fichier ne sont pas triées.

comm: file 1 is not in sorted order

comm a été spécialement conçu pour ce genre de cas d'utilisation, mais il nécessite une entrée triée.

awk est sans doute un meilleur outil pour cela car il est assez simple de trouver la différence ensemble, ne nécessite pas sort et offre une flexibilité supplémentaire.

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

Peut-être, par exemple, vous souhaitez trouver que la différence dans les lignes qui représentent des nombres non négatifs:

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

Peut-être vous avez besoin d'une meilleure façon de le faire dans Postgres, je peux à peu près à parier que vous ne trouverez pas un moyen plus rapide de le faire en utilisant des fichiers plats. Vous devriez être en mesure de faire une jointure interne simple et en supposant que les deux id sont Col. indexés qui devrait être très rapide.

Alors, ce qui est légèrement différent des autres réponses. Je ne peux pas dire qu'un compilateur C ++ est exactement un « outil Linux CLI », mais en cours d'exécution g++ -O3 -march=native -o set_diff main.cpp (avec le code ci-dessous main.cpp peut faire l'affaire):

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

Pour utiliser, il suffit de lancer set_diff B A ( pas A B, depuis B est nodes_to_keep) et la différence résultant sera imprimé à stdout.

Notez que j'ai renoncé à quelques meilleures pratiques C de conserver le code plus simple.

De nombreuses optimisations de vitesse supplémentaires pourraient être (au prix de plus de mémoire). mmap serait également particulièrement utile pour les grands ensembles de données, mais qui ferait le code beaucoup plus impliqué.

Puisque vous avez mentionné que les ensembles de données sont grandes, je pensais que la lecture d'une ligne nodes_to_delete à un moment peut-être une bonne idée de réduire la consommation de mémoire. L'approche adoptée dans le code ci-dessus ne sont pas particulièrement efficace s'il y a beaucoup de dupes dans votre nodes_to_delete. En outre, l'ordre ne se conserve pas.


Quelque chose plus facile à copier et coller dans bash (à savoir sauter création 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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top