2013-08-19 72 views
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 

非常感謝您的幫助。

+0

你有什麼試過?請注意,你發佈了一個相關的問題http://stackoverflow.com/questions/18273941/compare-rows-and-print-the-same-values-for-the-same-rows,你可以重新使用一些代碼。 – fedorqui

+0

任何人都有魔杖? – devnull

+0

你在這裏填充新生感覺如何?並請更正您的標題中的錯字。 – Arun

回答

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。該解決方案沒有考慮到具有相同範圍的行是連續的,所以我不必將所有內容都保存在內存中。這個解決方案比較簡單(特殊!),但是使用的內存比需求更多。

我建議您閱讀perlreftut,perlreperldsc手冊,以瞭解所有這些內容。