mesclar vários arquivos com base na primeira coluna e economizar problemas de memória

mesclar vários arquivos com base na primeira coluna e economizar problemas de memória

Eu tenho vários arquivos fastq delimitados por tabulações. Quero corresponder a segunda linha de cada leitura e somar os valores próximos a ela, se corresponderem. por exemplo:


file1.fq
>1
ATGCCGTT      file1:1
+
HHHHKKKK

file2.fq
>2
ATGCCGTT       file2:3
+
JJKHHTTT

>3
ATTCCAAC        file2:1
+
=#GJLMNB

A saída que eu quero é como:


output.txt

ATGCCGTT      file1:1    file2:3     count:4
ATTCCAAC      file2:1          count:1

O código que escrevi é:



#!/usr/bin/env perl
use strict;
use warnings;
no warnings qw( numeric );
my %seen;


$/ = "";
while () {
    chomp;
    my ($key, $value) = split ('\t', $_);

    my @lines = split /\n/, $key;
    my $key1 = $lines[1];

    $seen{$key1} //= [ $key ];
    push (@{$seen{$key1}}, $value);

}

foreach my $key1 ( sort keys %seen ) {
my $tot = 0;
my $file_count = @ARGV;
for my $val ( @{$seen{$key1}} ) {
        $tot += ( split /:/, $val )[0];
    }   

if ( @{ $seen{$key1} } >= $file_count) {


        print join( "\t", @{$seen{$key1}});
        print "\tcount:". $tot."\n\n";
    }
}

Este código funciona bem para arquivos pequenos, mas quando quero comparar arquivos grandes ele ocupa toda a memória, resultando na execução do script sem resultados. Quero modificar o script para que não ocupe memória. Não quero usar nenhum módulo. Acho que se eu carregar apenas um arquivo na memória por vez, isso economizará memória, mas não será possível. Por favor, ajude a modificar meu script.

Responder1

Você tentou awk? Não tenho certeza se ele lidará melhor com arquivos grandes, perlmas pode valer a pena tentar:

Dentro do seu script awk:

BEGIN {
    RS=">[0-9]+"
}

FNR==1{next}

NR==FNR {
    a[$1]++
    next
}


$1 in a {
    b[$1]++
    next
}

{
    c[$1]++
}

END {
    for (key in a) {
        if (b[key] == "") {
            printf key"\tfile1:"a[key]"\t\tcount:"a[key]"\n"
        } else {
            printf key"\tfile1:"a[key]"\tfile2:"b[key]"\tcount:"a[key]+b[key]"\n"
        }
    }
    for (key in c) {
        printf key"\t\tfile2:"c[key]"\tcount:"c[key]"\n"
    }
}

Para executá-lo:

$ awk -f myscript.awk file1 file2 > output.txt

Testei com:

arquivo1

>1
ATGCCGTT      file1:1
+
HHHHKKKK

>2
ATTCCAACg        file2:1
+
=#GJLMNB

arquivo2

>2
ATGCCGTT       file2:3
+
JJKHHTTT

>3
ATTCCAAC        file2:1
+
=#GJLMNB

Saída no terminal:

ATTCCAACg   file1:1         count:1
ATGCCGTT    file1:1 file2:1 count:2
ATTCCAAC            file2:1 count:1

Responder2

Adicione esses encantamentos místicos ao seu programa

use DB_File;
my %seen; 
unlink '/tmp/translation.db';
sleep 2; 
tie ( %seen, 'DB_File', '/tmp/translation.db' )
    or die "Can't open /tmp/translation.db\n";

e seu hash não residirá mais na memória, mas em um banco de dados em disco. Você pode deixar o resto do seu código exatamente como está. É verdade que usei o módulo DB_File, mas não há razão para não fazê-lo. Ele vem com cadaperlinstalação pronta para uso para que você não precise instalá-lo nem nada.

Eu uso essa abordagem o tempo todo se meus hashes estão ficando realmente grandes e descubro que, depois de passar por algum ponto de abraço vagamente definido, as coisas aceleram um pouco.

informação relacionada