Output parte di ogni riga in un file separato


14

Ho un file come questo:

a   AGTACTTCCAGGAACGGTGCACTCTCC
b   ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT
c   ATATTAAATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCATCCACTCCACAC
d   ATCAGTTTAATATCTGATACGTCCTCTATCCGAGGACAATATATTAAATGGA
e   TTTGGCTAAGATCAAGTGTAGTATCTGTTCTTATAAGTTTAATATCTGATATGTCCTCTATCTGA

Voglio fare un file a.seqche contenga una sequenza AGTACTTCCAGGAACGGTGCACTCTCC. Allo stesso modo b.seqcontiene ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT. In breve, Column1 dovrebbe essere usato come nome del file di output con estensione .seqe quindi dovrebbe avere la corrispondente sequenza column2 in esso. Posso farlo scrivendo uno script perl ma qualsiasi cosa sulla riga di comando sarà utile. Spero di sentirti presto.

Risposte:


16

La mia risposta istantanea sarebbe stata awkma se stai elaborando molte linee - e sto parlando di milioni - probabilmente vedrai un vero vantaggio dal passaggio a un "vero" linguaggio di programmazione.

Con questo in mente (e awkgià considerato come una risposta) ho scritto alcune implementazioni in diverse lingue e le ho confrontate sullo stesso set di dati da 10.000 righe su 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

A prima vista, la C sembra la migliore, ma era un maiale correre così veloce. Pypy e C ++ sono molto più facili da scrivere ed eseguire abbastanza bene a meno che tu non stia parlando di miliardi di righe. Se così fosse, un aggiornamento per fare tutto ciò in RAM o su un SSD potrebbe essere un investimento migliore di un miglioramento del codice.

Ovviamente nel tempo che ho passato attraverso questi, probabilmente avresti potuto elaborare alcune centinaia di milioni di dischi nell'opzione più lenta . Se riesci solo a scrivere awko creare loop di Bash, fallo e vai avanti con la vita. Ho avuto chiaramente troppo tempo libero oggi.

Ho anche testato alcune opzioni multi-thread (in C ++ e Python e ibridi con GNU parallel) ma l'overhead dei thread supera completamente qualsiasi vantaggio per un'operazione così semplice (suddivisione delle stringhe, scrittura).

Perl

awk( gawkqui) sarebbe sinceramente il mio primo punto di riferimento per testare dati come questo, ma puoi fare cose abbastanza simili in Perl. Sintassi simile ma con una maniglia di scrittura leggermente migliore.

perl -ane 'open(my $fh, ">", $F[0].".seq"); print $fh $F[1]; close $fh;' infile

Pitone

Mi piace Python. È la mia lingua del lavoro quotidiano ed è solo una lingua piacevole, solida e incredibilmente leggibile. Anche un principiante potrebbe probabilmente indovinare cosa sta succedendo qui.

with open("infile", "r") as f:
    for line in f:
        id, chunk = line.split()
        with open(id + ".seq", "w") as fw:
            fw.write(chunk)

Devi ricordare che il pythonbinario della tua distribuzione non è l'unica implementazione di Python là fuori. Quando ho eseguito lo stesso test tramite Pypy, è stato più veloce di C senza ulteriori ottimizzazioni logiche. Tienilo a mente prima di scrivere Python come "linguaggio lento".

C

Ho iniziato questo esempio per vedere cosa potremmo davvero fare per far funzionare la mia CPU ma, francamente, C è un incubo da programmare se non lo tocchi da molto tempo. Questo ha il rovescio della medaglia di essere limitato a linee da 100 caratteri anche se è molto semplice espanderlo, non ne avevo proprio bisogno.

La mia versione originale era più lenta di C ++ e pypy, ma dopo aver scritto sul blog ho ricevuto un aiuto da Julian Klode . Questa versione è ora la più veloce a causa dei suoi buffer IO ottimizzati. È anche molto più lungo e coinvolto più di ogni altra cosa.

#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 ++

Si comporta bene ed è molto più facile da scrivere rispetto al vero C. Hai ogni sorta di cose che ti tengono per mano (specialmente quando si tratta di stringhe e input). Tutto ciò significa che puoi effettivamente semplificare la logica. strtokin C è un porco perché elabora l'intera stringa e quindi dobbiamo fare tutta quella noiosa allocazione di memoria. Questo scorre lungo la linea fino a quando non tocca la linguetta e tiriamo fuori i segmenti di cui abbiamo bisogno.

#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

(Non la versione moreutils). È una bella sintassi concisa ma OMGSLOW. Potrei usarlo male.

parallel --colsep '\t' echo {2} \> {1}.seq <infile

Test del generatore di cavi

Ecco il mio generatore di dati per 100000 linee di [ATGC] * 64. Non è veloce e i miglioramenti sono i benvenuti.

cat /dev/urandom | tr -dc 'ATGC' | fold -w 64 | awk 'NR>100000{exit}{printf NR"\t"$0"\n"}' > infile

2
Vorrei sottolineare che elencare tutte le opzioni per le prestazioni può essere tanto dispendioso quanto la sola cosa che mi viene in mente. awkè ancora una buona risposta per qualcosa di meno di decine di milioni. Anche se ridimensionate [linearmente] fino a un miliardo di righe, C vi fa risparmiare solo 1,5 ore su Perl e 3,6 ore su awk.
Oli,

Ora il mio C ++ versione è in c'è così molto più veloce, forse mi piacerebbe prendere in considerazione C ++ per più semplice elaborazione di testo di grandi insiemi di dati. È quasi il doppio della velocità e questa è la differenza di molte ore quando si arriva a miliardi di linee.
Oli,



1
Penso che la velocità di generazione del tuo cablaggio di prova sia vincolata dal generatore di numeri casuali. Si può rendere più veloce utilizzando ogni numero dà o generare una distribuzione omogenea, ad esempio: paste <(yes A) <(yes T) <(yes G) <(yes C) | head -n1600000 | tr '\t' '\n' | shuf | tr -d \\n | fold -w64 | cat -n > infile.
Thor,

13

Implementazione pure shell:

while read -r filename content ; do
    printf '%s\n' "$content" >> "${filename}.seq"
done < /source/file

12

Utilizzando awk:

awk '{printf "%s\n", $2>$1".seq"}' file

Dai nominati file, stampare il secondo campo in ciascun record ( $2) su un file che prende il nome dal primo campo ( $1) con l' .seqaggiunta del nome.

Come sottolinea Thor nei commenti, per un set di dati di grandi dimensioni, è possibile esaurire i descrittori di file, quindi sarebbe saggio chiudere ogni file dopo aver scritto :

awk '{printf "%s\n", $2>$1".seq"; close($1".seq")}' file

Ciao Questo funziona Grazie mille .. Puoi spiegare un po 'il codice?
user3138373

@ user3138373 Spero che ti aiuti ...
Jasonwryan,

Aiuta .. Grazie Perché non funziona la stampa invece di printf ??
user3138373

3
Se ci sono molte righe, verranno utilizzati tutti i descrittori di file disponibili, quindi probabilmente dovresti aggiungere un close($1".seq").
Thor,

1
@Thor, true. awkTuttavia, alcune implementazioni come GNU sanno come aggirare il problema.
Stéphane Chazelas,

3

Ecco un modo per farlo con GNU sed:

<infile sed -r 's:(\w+)\s+(\w+):echo \2 > \1.seq:e; d'

O in modo più efficiente, come suggerito da Glenn Jackman :

<infile sed -r 's:(\w+)\s+(\w+):echo \2 > \1.seq:' | sh

1
Anche se è bello, è abbastanza inefficiente, dover generare un comando esterno per ogni linea. Sarebbe un po 'meglio avere la sed che emette tutti i comandi grezzi e reindirizzare l'output in "sh"
glenn jackman

1
@glennjackman: questo era solo un modo alternativo interessante per farlo. Se l'input è grande, awkè probabilmente lo strumento più efficiente da utilizzare. Naturalmente hai ragione a non spawnare shper ogni riga, ho aggiunto l'opzione pipe come alternativa.
Thor,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.