多么有趣的问题。我将在 Perl 中回答。
我们需要一次读取同一范围的所有行。这些范围内的每个数字还必须记住它们来自哪一行。然后,我们可以对每个范围的数字进行排序,并重新组合行。
对于第一个范围,我们将有一个值的集合,例如
[213 => 1], [253 => 1], [295 => 1], [350 => 1],
[260 => 2], [295 => 2], [315 => 2],
[205 => 3], [263 => 3], [295 => 3],
我们应该对常见的数字进行重复数据删除,这样我们就可以得到
[213 => 1], [253 => 1], [295 => 1, 2, 3], [350 => 1],
[260 => 2], [315 => 2],
[205 => 3], [263 => 3],
(顺序并不重要)。
我们可以按第一个字段对这些项目进行排序:
my @sorted = sort { $a->[0] <=> $b->[0] } @items;
对于每一行,我们可以遍历已排序的项目,并根据行号决定是否打印一个NA
或一个数字:
for my $line (1 .. 3) {
my @fields = map { decide_if_number_or_na($line, @$_) } @sorted;
...
}
sub decide_if_number_or_na {
my ($line, $number, @lines) = @_;
return $number if grep { $line == $_ } @lines; # ... if any of the lines is our line
return "NA";
}
当然,我们应该立即发出正确的0
或1
值。
将所有这些联系在一起有点复杂。在解析输入的过程中,我们需要将每一行与当前01
模式相关联,记住前两个字段,并为项目构建数据结构。
生成的代码遵循上述考虑,但采取了一些捷径:一旦它们被排序,每个数字的实际值对我们的项目并不重要,我们可以丢弃它。
use strict; use warnings; use feature 'say';
my @lines; # an array of hashes, which hold information about each line
my %ranges; # a hash mapping range identifiers to number-to-occuring-line-array hashes
while (<>) {
chomp;
my ($letter, $range, @nums) = split; # split everything into field ...
my @pattern = split //, pop @nums; # but the last field is a pattern, which we split into chars.
push @{ $ranges{$range}{$_} }, $. for @nums; # $. is the line no
push @lines, {
letter => $letter,
range => $range,
pattern => \@pattern,
line => $.,
};
}
# simplify and sort the ranges:
for my $key (keys %ranges) {
my $nums2lines = $ranges{$key}; # get the number-to-occuring-lines-array hashes
# read the next statement bottom to top:
my @items =
map { $nums2lines->{$_} } # 3. get the line number arrayref only (forget actual number, now that they are ordered)
sort { $a <=> $b } # 2. sort them numerically
keys %$nums2lines; # 1. get all numbers
$ranges{$key} = \@items; # Remember these items at the prior position
}
# Iterate through all lines
for my $line (@lines) {
# Unpack some variables
my @pattern = @{ $line->{pattern} };
my $lineno = $line->{line};
my $items = $ranges{$line->{range}};
# For each item, emit the next part of the pattern, or NA.
my @fields = map { pattern_or_na($lineno, @$_) ? shift @pattern : "NA" } @$items;
say join "\t", $line->{letter}, $line->{range}, @fields;
}
sub pattern_or_na {
my ($line, @lines) = @_; # the second value (the specific number)
return scalar grep { $_ == $line } @lines; # returns true if a number is on this line
}
这会产生所需的输出。
这是相当复杂的代码,尤其是对于初学者。它利用 Perl 引用和autovivifiction。此外,我使用了许多列表转换,例如sort
,map
或grep
. 该解决方案没有考虑到具有相同范围的行是连续的,因此我不必将所有内容都保存在内存中。这个解决方案更简单(原文如此!)但使用的内存比必要的多。
我建议阅读perlreftut
、perlre
和perldsc
联机帮助页以了解所有这些内容。