当只需要一部分字段时,分割长行的高效方法是什么
What is a performant way to split long lines when only a subset of fields is desired
我的查询详情如下:
- 我有一个非常大的 TSV(Tab 九月值)文件(大于 30 GB)。
- 我想从此文件中提取不以最后一个空字段结尾的某些行。由于这是一个 TSV 文件,那些不以
\t\n
结尾的行是一个简单的测试,而不是这个问题的主题。这将立即删除大约 75% 的行,从而减少工作量。
- 然后我想从剩余的行中提取一小部分字段。这些字段不连续,但数量很少(例如,假设总共有 30 多个字段中有 7 个字段)。例如,假设字段
2,3,12-18,25-28,31
.
- 我提取的行很长,大部分长达 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`
我的查询详情如下:
- 我有一个非常大的 TSV(Tab 九月值)文件(大于 30 GB)。
- 我想从此文件中提取不以最后一个空字段结尾的某些行。由于这是一个 TSV 文件,那些不以
\t\n
结尾的行是一个简单的测试,而不是这个问题的主题。这将立即删除大约 75% 的行,从而减少工作量。 - 然后我想从剩余的行中提取一小部分字段。这些字段不连续,但数量很少(例如,假设总共有 30 多个字段中有 7 个字段)。例如,假设字段
2,3,12-18,25-28,31
. - 我提取的行很长,大部分长达 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`