当只需要一部分字段时,分割长行的高效方法是什么

What is a performant way to split long lines when only a subset of fields is desired

我的查询详情如下:

  1. 我有一个非常大的 TSV(Tab 九月值)文件(大于 30 GB)。
  2. 我想从此文件中提取不以最后一个空字段结尾的某些行。由于这是一个 TSV 文件,那些不以 \t\n 结尾的行是一个简单的测试,而不是这个问题的主题。这将立即删除大约 75% 的行,从而减少工作量。
  3. 然后我想从剩余的行中提取一小部分字段。这些字段不连续,但数量很少(例如,假设总共有 30 多个字段中有 7 个字段)。例如,假设字段 2,3,12-18,25-28,31.
  4. 我提取的行很长,大部分长达 1,000 个字符,因为它们包含大量制表符分隔字段。

一种选择显然是使用以下简单代码,我已尝试对其进行很好的格式化并包含注释以显示我的推理:

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 中)求助于基于大块的读取。

你可以用它的限制参数告诉split什么时候停止:

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

(指定比您实际需要的字段数多一个,因为它生成的最后一个字段将包含该行的剩余部分。)

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`