Какой эффективный способ разбить длинные строки, когда требуется только подмножество полей - PullRequest
1 голос
/ 19 июня 2019

Подробности моего запроса приведены ниже:

  1. У меня есть очень большой файл TSV (Tab Sep. Value) (где большой> 30 ГБ).
  2. Я хочу извлечь из этого файла определенные строки, которые не заканчиваются пустым последним полем. Поскольку это файл TSV, эти строки не заканчиваются \t\n, что является тривиальным тестом и не является предметом этого вопроса. Это удалит около 75% линий, сразу же, уменьшив нагрузку.
  3. Затем я хочу извлечь небольшое подмножество полей из оставшихся строк. Поля не являются смежными, но их немного (например, скажем, семь из чуть более тридцати в общей сложности). Например, скажем, поля 2,3,12-18,25-28,31.
  4. Строки, из которых я извлекаю, очень длинные, большинство длиной до 1000 символов, поскольку они содержат большое количество полей с разделителями табуляции.

Один из вариантов - это, очевидно, использовать следующий простой код, который я попытался красиво отформатировать и включить комментарии, чтобы показать мои рассуждения:

use warnings;
use strict;
# I am using the latest stable version of Perl for this exercise
use 5.30.0;

while (<>)
{
  # Skip lines ending with an empty field
  next if substr($_,-2) eq "\t\n";

  # Remove "\n"
  chomp;

  # Split matching lines into fields on "\t", creating @fields
  my @fields=split(/\t/,$_);

  # Copy only the desired fields from @fields to create a new
  # line in TSV format
  # This can be done in one simple step in Perl, using
  # array slices and the join() function
  my $new_line=join("\t",@fields[2,3,12..18,25..28,31]);

  # ...
}

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

Правильно ли я оцениваю или просто делаю простой split, за которым следует join срезов, содержащих интересующие вас поля, лучший способ добраться сюда с точки зрения производительности?

Обновление : К сожалению, никто не упомянул о возможности использования GNU cut для разделения и передачи результатов в Perl для остальной части обработки. Это, вероятно, самый эффективный способ, без написания большого количества пользовательского (C) кода для этого или обращения к чтению на основе больших блоков с пользовательским разбором строк (также в C).

Ответы [ 2 ]

5 голосов
/ 19 июня 2019

Вы можете указать split, когда останавливаться, с помощью параметра limit:

my @fields=split(/\t/,$_,33);

(Укажите больше, чем количество полей, которые вы на самом деле хотите, потому что последнее поле, которое оно создает, будет содержать остаток строки.)

0 голосов
/ 17 июля 2019
grep -P -v "\t\s*$" yourFile.tsv | cut -f2,3,12-18,25-28,31

Вам даже не нужно писать код perl для этого.

Здесь

-P - это "perl grep", который предоставляет больше возможностей для наивного grep.

-v - это обратное сопоставление, которое соответствует вашему next if

КСТАТИ, если у вас достаточно ядер и памяти, то вы можете ускорить процесс путем разделения и объединения как:

split -n 10 -d yourFile.tsv yourFile.tsv.

Это сгенерирует yourFile.tsv.00, ..., yourFile.tsv.09

Таким образом, весь код выглядит примерно так, как показано в блоке ниже:

`split -n 10 -d yourFile.tsv yourFile.tsv.`
@yourFiles = `ls yourFile.tsv.*`;
foreach $file (@yourFiles) {
      `grep -P -v "\t\s*$" $file | cut -f2,3,12-18,25-28,31 > $file.filtered &`;
}
`cat yourFile.*.filtered > final.output.tsv`
...