Ma réponse instantanée aurait été, awk
mais si vous traitez beaucoup de lignes - et je parle de millions - vous verrez probablement un réel avantage à passer à un «vrai» langage de programmation.
Dans cet esprit (et awk
déjà pris comme réponse), j'ai écrit quelques implémentations dans différents langages et les ai comparées sur le même ensemble de données de 10 000 lignes sur un SSD PCI-E.
me* (C) 0m1.734s
me (C++) 0m1.991s
me (Python/Pypy) 0m2.390s
me (perl) 0m3.024s
Thor+Glenn (sed|sh) 0m3.353s
me (python) 0m3.359s
jasonwryan+Thor (awk) 0m3.779s
rush (while read) 0m6.011s
Thor (sed) 1m30.947s
me (parallel) 4m9.429s
En un coup d'œil, le C est le plus beau, mais c'était un cochon pour courir aussi vite. Pypy et C ++ sont beaucoup plus faciles à écrire et à exécuter suffisamment bien, sauf si vous parlez de plusieurs milliards de lignes. Si tel était le cas, une mise à niveau pour tout faire en RAM ou sur un SSD pourrait être un meilleur investissement qu'une amélioration de code.
Évidemment, pendant le temps que j'ai passé à les parcourir, vous auriez probablement pu traiter quelques centaines de millions d'enregistrements dans l'option la plus lente . Si vous ne pouvez écrire que des awk
boucles Bash, faites-le et poursuivez votre vie. J'ai clairement eu trop de temps libre aujourd'hui.
J'ai également testé quelques options multi-thread (en C ++ et Python et hybrides avec GNU parallel
) mais la surcharge des threads l'emporte complètement sur tout avantage pour une opération aussi simple (division de chaîne, écriture).
Perl
awk
( gawk
ici) serait honnêtement ma première escale pour tester des données comme celle-ci, mais vous pouvez faire des choses assez similaires en Perl. Syntaxe similaire mais avec une poignée d'écriture légèrement meilleure.
perl -ane 'open(my $fh, ">", $F[0].".seq"); print $fh $F[1]; close $fh;' infile
Python
J'aime Python. C'est ma langue de travail de jour et c'est juste une langue agréable, solide et incroyablement lisible. Même un débutant pourrait probablement deviner ce qui se passe ici.
with open("infile", "r") as f:
for line in f:
id, chunk = line.split()
with open(id + ".seq", "w") as fw:
fw.write(chunk)
Vous devez vous rappeler que le python
binaire de votre distribution n'est pas la seule implémentation de Python. Lorsque j'ai exécuté ce même test via Pypy, il était plus rapide que C sans aucune optimisation logique supplémentaire. Gardez cela à l'esprit avant d'écrire Python comme un "langage lent".
C
J'ai commencé cet exemple pour voir ce que nous pourrions vraiment faire pour mon processeur, mais franchement, C est un cauchemar à coder si vous ne l'avez pas touché depuis longtemps. Cela a l'inconvénient supplémentaire d'être limité aux lignes de 100 caractères, bien qu'il soit très simple de l'étendre, je n'en avais tout simplement pas besoin.
Ma version originale était plus lente que C ++ et pypy mais après avoir blogué à ce sujet, j'ai obtenu de l' aide de Julian Klode . Cette version est maintenant la plus rapide en raison de ses tampons IO modifiés. C'est aussi beaucoup plus long et plus complexe qu'autre chose.
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#define BUFLEN (8 * 1024)
int main(void) {
FILE *fp;
FILE *fpout;
char line[100];
char *id;
char *token;
char *buf = malloc(BUFLEN);
fp = fopen("infile", "r");
setvbuf ( fp , buf , _IOLBF, BUFLEN );
while (fgets(line, 100, fp) != NULL) {
id = strtok(line, "\t");
token = strtok(NULL, "\t");
char *fnout = malloc(strlen(id)+5);
fnout = strcat(fnout, id);
fnout = strcat(fnout, ".seq");
fpout = fopen(fnout, "w");
setvbuf ( fpout , NULL , _IONBF , 0 );
fprintf(fpout, "%s", token);
fclose(fpout);
}
fclose(fp);
return 0;
}
C ++
Fonctionne bien et est beaucoup plus facile à écrire que le vrai C. Vous avez toutes sortes de choses qui vous tiennent par la main (surtout en ce qui concerne les cordes et les entrées). Tout cela signifie que vous pouvez réellement simplifier la logique. strtok
en C est un porc car il traite la chaîne entière et ensuite nous devons faire toute cette allocation de mémoire fastidieuse. Cela flotte juste le long de la ligne jusqu'à ce qu'il touche l'onglet et nous retirons les segments lorsque nous en avons besoin.
#include <fstream>
#include <string>
using namespace std;
int main(void) {
ifstream in("infile");
ofstream out;
string line;
while(getline(in, line)) {
string::size_type tab = line.find('\t', 0);
string filename = line.substr(0, tab) + ".seq";
out.open(filename.c_str());
out << line.substr(tab + 1);
out.close();
}
in.close();
}
GNU Parallel
(Pas la version moreutils). C'est une belle syntaxe concise mais OMGSLOW. Je l'utilise peut-être mal.
parallel --colsep '\t' echo {2} \> {1}.seq <infile
Générateur de faisceaux d'essai
Voici mon générateur de données pour 100 000 lignes de [ATGC] * 64. Ce n'est pas rapide et les améliorations sont les bienvenues.
cat /dev/urandom | tr -dc 'ATGC' | fold -w 64 | awk 'NR>100000{exit}{printf NR"\t"$0"\n"}' > infile
awk
est toujours une bonne réponse pour rien de moins que des dizaines de millions. Même si vous augmentez [linéairement] cela jusqu'à un milliard de lignes, C ne vous fait économiser que 1,5 heure sur Perl et 3,6 heures sur awk.paste <(yes A) <(yes T) <(yes G) <(yes C) | head -n1600000 | tr '\t' '\n' | shuf | tr -d \\n | fold -w64 | cat -n > infile
.Implémentation pure shell:
la source
En utilisant
awk
:À partir du nominé
file
, imprimez le deuxième champ de chaque enregistrement ($2
) dans un fichier nommé d'après le premier champ ($1
) avec en.seq
annexe au nom.Comme Thor le souligne dans les commentaires, pour un grand ensemble de données, vous pouvez épuiser les descripteurs de fichier, il serait donc sage de fermer chaque fichier après l'écriture :
la source
close($1".seq")
.awk
implémentations comme GNU savent comment contourner cela.Voici une façon de le faire avec GNU sed:
Ou plus efficacement, comme l'a suggéré glenn jackman :
la source
awk
c'est probablement l'outil le plus efficace à utiliser. Vous avez bien sûr raison de ne pas frayersh
pour chaque ligne, j'ai ajouté l'option pipe comme alternative.