我有一个输入:
A 200-400 213 253 295 350 0011
A 200-400 260 295 315 000
A 200-400 205 263 295 111
B 800-900 801 832 840 843 870 890 895 00110101
B 800-900 801 823 850 010
B 800-900 850 1
.
.
.
0和最后一列中的1个值对应于从第三列到最后一列的值
我想生成一个与制表符分隔的矩阵,如下所示:
A 200-400 NA 213 253 NA NA 295 NA 350
A 200-400 NA NA NA 260 NA 295 315 NA
A 200-400 205 NA NA NA 263 295 NA NA
B 800-900 801 NA 832 840 843 NA 870 890 895 900
B 800-900 801 823 NA NA NA 850 NA NA NA NA
B 800-900 NA NA NA NA NA 850 NA NA NA NA
最后,将0和1值替换为相应的值和
A 200-400 NA 0 0 NA NA 1 NA 1
A 200-400 NA NA NA 0 NA 0 0 NA
A 200-400 1 NA NA NA 1 1 NA NA
B 800-900 0 NA 0 1 1 NA 0 1 0 1
B 800-900 0 1 NA NA NA 0 NA NA NA NA
B 800-900 NA NA NA NA NA 1 NA NA NA NA
非常感谢您的帮助。
答案 0 :(得分:2)
多么有趣的问题。我将在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
联机帮助页,以便了解所有这些内容。