0

我有这样的输入:

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

非常感谢您的帮助。

4

1 回答 1

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";
}

当然,我们应该立即发出正确的01值。

将所有这些联系在一起有点复杂。在解析输入的过程中,我们需要将每一行与当前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,mapgrep. 该解决方案没有考虑到具有相同范围的行是连续的,因此我不必将所有内容都保存在内存中。这个解决方案更简单(原文如此!)但使用的内存比必要的多。

我建议阅读perlreftutperlreperldsc联机帮助页以了解所有这些内容。

于 2013-08-19T15:41:49.483 回答