なんて楽しい質問でしょう。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
or1
値をすぐに発行する必要があります。
これらすべてを結びつけるのは少し複雑です。入力の解析中に、各行を現在の01
パターンに関連付け、最初の 2 つのフィールドを記憶し、項目のデータ構造を構築する必要があります。
結果のコードは上記の考慮事項に従いますが、いくつかのショートカットを使用します: 順序付けが行われると、各数値の実際の値はアイテムにとって重要ではないため、破棄できます。
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 の参照とautovivificationを利用します。sort
また、 、 、などの多くのリスト変換を使用しmap
ますgrep
。このソリューションでは、同じ範囲の行が連続していることを考慮していないため、すべてをメモリに保持する必要はありません。このソリューションはより単純ですが (sic!)、必要以上のメモリを使用します。
これらすべてを理解するには、、、、およびマンページをperlreftut
読むperlre
ことをお勧めします。perldsc