разобрать огромный текстовый файл в perl - PullRequest
3 голосов
/ 15 ноября 2011

У меня есть текстовый файл, разделенный табуляцией. Они могут быть довольно большими до 1 ГБ. У меня будет переменное количество столбцов в зависимости от количества образцов в них. Каждый образец имеет восемь столбцов. Например, sampleA: ID1, id2, MIN_A, AVG_A, MAX_A, AR1_A, AR2_A, AR_A, AR_5. Из которых ID1 и ID2 являются общими для всех образцов. Чего я хочу добиться, так это разбить весь файл на куски файлов в зависимости от количества выборок.

ID1,ID2,MIN_A,AVG_A,MAX_A,AR1_A,AR2_A,AR3_A,AR4_A,AR5_A,MIN_B, AVG_B, MAX_B,AR1_B,AR2_B,AR3_B,AR4_B,AR5_B,MIN_C,AVG_C,MAX_C,AR1_C,AR2_C,AR3_C,AR4_C,AR5_C
12,134,3535,4545,5656,5656,7675,67567,57758,875,8678,578,57856785,85587,574,56745,567356,675489,573586,5867,576384,75486,587345,34573,45485,5447
454385,3457,485784,5673489,5658,567845,575867,45785,7568,43853,457328,3457385,567438,5678934,56845,567348,58567,548948,58649,5839,546847,458274,758345,4572384,4758475,47487

Вот так выглядит мой файл модели, я хочу, чтобы он был:

File A : 
ID1,ID2,MIN_A,AVG_A,MAX_A,AR1_A,AR2_A,AR3_A,AR4_A,AR5_A
12,134,3535,4545,5656,5656,7675,67567,57758,875
454385,3457,485784,5673489,5658,567845,575867,45785,7568,43853

File B:
ID1, ID2,MIN_B, AVG_B, MAX_B,AR1_B,AR2_B,AR3_B,AR4_B,AR5_B
12,134,8678,578,57856785,85587,574,56745,567356,675489
454385,3457,457328,3457385,567438,5678934,56845,567348,58567,548948

File C:

ID1, ID2,MIN_C,AVG_C,MAX_C,AR1_C,AR2_C,AR3_C,AR4_C,AR5_C
12,134,573586,5867,576384,75486,587345,34573,45485,5447
454385,3457,58649,5839,546847,458274,758345,4572384,4758475,47487.

Есть ли простой способ сделать это, кроме тщательного изучения массива?

Как я сработал, моя логика подсчитывает (количество заголовков - 2), и, разделив их на 8, я получу количество сэмплов в файле. И затем, проходя каждый элемент в массиве и анализируя их. Кажется, утомительный способ сделать это. Я был бы счастлив узнать любой более простой способ справиться с этим.

Спасибо Sipra

Ответы [ 4 ]

8 голосов
/ 15 ноября 2011
#!/bin/env perl

use strict;
use warnings;

# open three output filehandles
my %fh;
for (qw[A B C]) {
  open $fh{$_}, '>', "file$_" or die $!;
}

# open input
open my $in, '<', 'somefile' or die $!;

# read the header line. there are no doubt ways to parse this to
# work out what the rest of the program should do.
<$in>;

while (<$in>) {
  chomp;
  my @data = split /,/;

  print $fh{A} join(',', @data[0 .. 9]), "\n";
  print $fh{B} join(',', @data[0, 1, 10 .. 17]), "\n";
  print $fh{C} join(',', @data[0, 1, 18 .. $#data]), "\n";
}

Обновление: Мне стало скучно, и он стал более умным, поэтому он автоматически обрабатывает любое количество записей из 8 столбцов в файле.К сожалению, у меня нет времени, чтобы объяснить это или добавить комментарии.

#!/usr/bin/env perl

use strict;
use warnings;

# open input
open my $in, '<', 'somefile' or die $!;

chomp(my $head = <$in>);
my @cols = split/,/, $head;

die 'Invalid number of records - ' . @cols . "\n"
  if (@cols -2) % 8;

my @files;
my $name = 'A';
foreach (1 .. (@cols - 2) / 8) {
   my %desc;
   $desc{start_col} = (($_ - 1) * 8) + 2;
   $desc{end_col}   = $desc{start_col} + 7;
   open $desc{fh}, '>', 'file' . $name++ or die $!;
   print {$desc{fh}} join(',', @cols[0,1],
                               @cols[$desc{start_col} .. $desc{end_col}]),
                     "\n";

   push @files, \%desc;
}

while (<$in>) {
  chomp;
  my @data = split /,/;

  foreach my $f (@files) {
    print {$f->{fh}} join(',', @data[0,1],
                               @data[$f->{start_col} .. $f->{end_col}]),
                   "\n";
   }
}
2 голосов
/ 16 ноября 2011

Это не зависит от количества образцов. Я не уверен в имени выходного файла, потому что вы можете получить более 26 образцов. Просто замените, как работает имя выходного файла, если это так. :)

use strict;
use warnings;

use File::Slurp;
use Text::CSV_XS;
use Carp qw( croak );

#I'm lazy
my @source_file = read_file('source_file.csv');
# you metion yours is tab separated
# just add the {sep_char => "\t"} inside new
my $csv = Text::CSV_XS->new()
  or croak "Cannot use CSV: " . Text::CSV_XS->error_diag();
my $output_file;

#read each row
while ( my $raw_line = shift @source_file ) {
    $csv->parse($raw_line);
    my @fields = $csv->fields();

    #get the first 2 ids
    my @ids = splice @fields, 0, 2;

    my $group = 0;
    while (@fields) {
        #get the first 8 columns
        my @columns = splice @fields, 0, 8;
        #if you want to change the separator of the output replace ',' with "\t"
        push @{ $output_file->[$group] }, (join ',', @ids, @columns), $/;
        $group++;
    }
}

#for filename purposes
my $letter = 65;
foreach my $data (@$output_file) {
    my $output_filename = sprintf( 'SAMPLE_%c.csv', $letter );
    write_file( $output_filename, @$data );
    $letter++;
}

#if you reach more than 26 samples then you might want to use numbers instead
#my $sample_number = 1;
#foreach my $data (@$output_file) {
#    my $output_filename = sprintf( 'sample_%s.csv', $sample_number );
#    write_file( $output_filename, @$data );
#    $sample_number++;
#}
0 голосов
/ 15 ноября 2011

Вот один вкладыш для печати первого образца, вы можете написать скрипт оболочки для записи данных для разных образцов в разные файлы

perl -F, -lane 'print "@F[0..1] @F[2..9]"' <INPUT_FILE_NAME>
0 голосов
/ 15 ноября 2011

Вы сказали, что табуляция разделена, но ваш пример показывает, что она разделена запятой.Насколько я понимаю, это ограничение на размещение ваших образцов данных в Markdown?

Я думаю, вы немного беспокоитесь о памяти, поэтому вы хотите открывать несколько файлов и записывать их при разборе большого файла.

Я бы сказал, чтобы попробовать Text :: CSV :: Simple .Однако я полагаю, что он считывает весь файл в память, что может быть проблемой для файла такого размера.

Довольно просто прочитать строку и поместить эту строку в список.Проблема заключается в сопоставлении полей в этом списке с именами самих полей.

Если вы читаете файл с циклом while, вы не читаете весь файл в память сразу.Если вы читаете в каждой строке, анализируете эту строку, а затем записываете эту строку в различные выходные файлы, вы не занимает много памяти.Там есть кеш, но я считаю, что он очищается после записи \n в файл.

Хитрость заключается в том, чтобы открыть входной файл, а затем прочитать в первой строке.Вы хотите создать некую структуру отображения полей, чтобы вы могли выяснить, какие поля записывать в каждый из выходных файлов.

У меня будет список всех файлов, в которые нужно записать.Таким образом, вы можете просмотреть список для каждого файла.Каждый элемент в списке должен содержать информацию, необходимую для записи в этот файл.

Во-первых, вам нужен дескриптор файла, чтобы вы знали, в какой файл вы пишете.Во-вторых, вам нужен список номеров полей, которые вы должны записать в этот конкретный выходной файл.

Я вижу какой-то цикл обработки, подобный следующему:

while (my $line = <$input_fh>) {   #Line from the input file.
   chomp $line;
   my @input_line_array = split /\t/, $line;
   my $fileHandle;
   foreach my $output_file (@outputFileList) {  #List of output files.
       $fileHandle = $output_file->{FILE_HANDLE};
       my @fieldsToWrite;
       foreach my $fieldNumber (@{$output_file->{FIELD_LIST}}) {
          push $fieldsToWrite, $input_line_array[$field];
       }
       say $file_handle join "\t", @fieldsToWrite;
   }
}

I'mчтение в одну строку входного файла в $line и деление этого на поля, которые я помещаю в @input_line_array.Теперь, когда у меня есть строка, я должен выяснить, какие поля записываются в каждый из выходных файлов.

У меня есть список с именем @outputFileList, который представляет собой список всех выходных файлов, которые я хочу записатьк.$outputFileList[$fileNumber]->{FILE_HANDLE} содержит дескриптор файла для моего выходного файла $fileNumber.$ouputFileList[$fileNumber]->{FIELD_LIST} - это список полей, которые я хочу записать в выходной файл $fileNumber.Это индексируется для полей в @input_line_array.Так что если

$outputFileList[$fileNumber]->{FIELD_LIST} = [0, 1, 2, 4, 6, 8];

означает, что я хочу записать следующие поля в мой выходной файл: $input_line_array[0], $input_line_array[1], $input_line_array[2], $input_line_array[4], $input_line_array[6] и $input_line_array[8] вмой выходной файл $outputFileList->[$fileNumber]->{FILE_HANDLE} в этом порядке в виде списка, разделенного табуляцией.

Надеюсь, в этом есть какой-то смысл.

Первоначальной проблемой является чтение в первой строке <$input_fh> и синтаксический анализ его в необходимую сложную структуру.Однако теперь, когда у вас есть представление о том, как должна храниться эта структура, разбор этой первой строки не должен быть слишком большой проблемой.

Хотя в этом примере я не использовал объектно-ориентированный код (Я вытаскиваю это из моего ... я имею в виду ... мозг, когда я пишу этот пост).Я бы определенно использовал подход объектно-ориентированного кода с этим.Это на самом деле сделает вещи намного быстрее, удалив ошибки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...