2012-06-05 12 views
5

Ich bin zurück mit einer anderen Frage. Ich habe eine Liste von Daten:Perl vergleichen Elemente von Arrays und Gruppierung

1 L DIELTQSPE H EVQLQESDAELVKPGASVKISCKASGYTFTDHE 
2 L DIVLTQSPRVT H EVQLQQSGAELVKPGASIKDTY 
3 A ALQLTQSPSSLSAS B RITLKESGPPLVKPTCS C ELDKWAN 
4 A ALQLTQSPSSLSAS B RITLKESGPPLVKPTCS C ELDKWAG 
5 A ALQLTQSPSSLSAS B RITLKESGPPLVKPTCS C LELDKWASL 
6 L DIQMTQIPSSLSASLSIC H EVQLQQSGVEVKMSCKASGYTFTS 
7 L SYELTQPPSVSVSPGSIT H QVQLVQSAKGSGYSFS P YNKRKAFYTTKNIIG 
8 L SYELTQPPSVSVSPGRIT H EVQLVQSGAASGYSFS P NNTRKAFYATGDIIG 
9 A MPIMGSSVAVLAIL B DIVMTQSPTVTI C EVQLQQSGRGP 
10 A MPIMGSSVVLAIL B DIVMTQSPTVTI C EVQLQQSGRGP 
11 L DVVMTQTPLQ H EVKLDESVTVTSSTWPSQSITCNVAHPASSTKVDKKIE 
12 A DIVMTQSPDAQYYSTPYSFGQGTKLEIKR 

Und ich möchte die dritte Elemente & & 5. Elemente jeder Zeile vergleichen, dann die Gruppe, ob sie die gleichen 3. & & 5. Elemente haben. Zum Beispiel mit den oben genannten Daten werden die Ergebnisse sein:

3: 3 A ALQLTQSPSSLSAS B RITLKESGPPLVKPTCS C ELDKWAN 
    4 A ALQLTQSPSSLSAS B RITLKESGPPLVKPTCS C ELDKWAG 
    5 A ALQLTQSPSSLSAS B RITLKESGPPLVKPTCS C LELDKWASL 
9: 9 A MPIMGSSVAVLAIL B DIVMTQSPTVTI C EVQLQQSGRGP 
    10 A MPIMGSSVVLAIL B DIVMTQSPTVTI C EVQLQQSGRGP 

Fyi, in den eigentlichen Daten, die 3., 5., 7. Elemente sehr lang sind. Ich habe sie geschnitten, um das Ganze zu sehen.

Dies ist, was ich getan habe, ich weiß, es ist sehr ungeschickt, aber als Anfänger tue ich mein Bestes. Und das Problem ist, dass es nur die erste Gruppe der 'gleichen' Gruppe zeigt. Könnten Sie mir bitte zeigen, wo es schief gelaufen ist und/oder andere schöne Methoden, um das zu lösen, bitte?

my $file = <>; 
open(IN, $file)|| die "no $file: $!\n"; 
my @arr; 
while (my $line=<IN>){ 
     push @arr, [split (/\s+/, $line)] ; 
} 
close IN; 

my (@temp1, @temp2,%hash1); 
for (my $i=0;$i<=$#arr ;$i++) { 
    push @temp1, [$arr[$i][2], $arr[$i][4]]; 
    for (my $j=$i+1;$j<=$#arr ;$j++) { 
     push @temp2, [$arr[$j][2], $arr[$j][4]]; 
     if (($temp1[$i][0] eq $temp2[$j][0])&& ($temp1[$i][1] eq $temp2[$j][1])) { 
      push @{$hash1{$arr[$i][0]}}, $arr[$i], $arr[$j]; 
     } 
    } 
} 
print Dumper \%hash1; 
+0

Vielen Dank an alle. Alle deine Kommentare und Codes sind sehr hilfreich für mich. Danke, dass Sie meine "Scheindaten" korrigiert und weitere Schritte in Betracht gezogen haben. :-) – Krista

Antwort

2

Sie zu haben scheinen zu kompliziert diese ein bisschen mehr, als es sein muss, aber das ist üblich für Anfänger. Denken Sie mehr darüber nach, wie Sie dies manuell tun würden:

  • Schauen Sie sich jede Zeile an.
  • Sehen Sie, ob das dritte und fünfte Feld mit der vorherigen Zeile übereinstimmen.
  • Wenn ja, drucken Sie sie.

Der Looping und all das ist völlig unnötig:

#!/usr/bin/env perl 

use strict; 
use warnings; 

my ($previous_row, $third, $fifth) = ('') x 3; 

while (<DATA>) { 
    my @fields = split; 
    if ($fields[2] eq $third && $fields[4] eq $fifth) { 
    print $previous_row if $previous_row; 
    print "\t$_"; 
    $previous_row = ''; 
    } else { 
    $previous_row = $fields[0] . "\t" . $_; 
    $third = $fields[2]; 
    $fifth = $fields[4]; 
    } 
} 

__DATA__ 
1 L DIELTQSPE H EVQLQESDAELVKPGASVKISCKASGYTFTDHE 
2 L DIVLTQSPRVT H EVQLQQSGAELVKPGASIKDTY 
3 A ALQLTQSPSSLSAS B RITLKESGPPLVKPTCS C ELDKWAN 
4 A ALQLTQSPSSLSAS B RITLKESGPPLVKPTCS C ELDKWAG 
5 A ALQLTQSPSSLSAS B RITLKESGPPLVKPTCS C LELDKWASL 
6 L DIQMTQIPSSLSASLSIC H EVQLQQSGVEVKMSCKASGYTFTS 
7 L SYELTQPPSVSVSPGSIT H QVQLVQSAKGSGYSFS P YNKRKAFYTTKNIIG 
8 L SYELTQPPSVSVSPGRIT H EVQLVQSGAASGYSFS P NNTRKAFYATGDIIG 
9 A MPIMGSSVAVLAIL B DIVMTQSPTVTI C EVQLQQSGRGP 
10 A MPIMGSSVAVLAIL B DIVMTQSPTVTI C EVQLQQSGRGP 
11 L DVVMTQTPLQ H EVKLDESVTVTSSTWPSQSITCNVAHPASSTKVDKKIE 
12 A DIVMTQSPDAQYYSTPYSFGQGTKLEIKR 

(Beachten Sie, dass ich die Leitung 10 leicht verändert, so dass seine dritte Feldlinie 9 übereinstimmen, um die gleichen Gruppen in der Ausgabe zu erhalten, wie angegeben.)

Bearbeiten: Eine Codezeile wurde durch einen Kopier-/Einfügefehler dupliziert.

Edit 2: In Reaktion auf die Kommentare, hier ist eine zweite Version, die nicht davon ausgehen, dass die Linien, die aneinander grenzen gruppiert werden sollen:

#!/usr/bin/env perl 

use strict; 
use warnings; 

my @lines; 
while (<DATA>) { 
    push @lines, [ $_, split ]; 
} 

# Sort @lines based on third and fifth fields (alphabetically), then on 
# first field/line number (numerically) when third and fifth fields match 
@lines = sort { 
    $a->[3] cmp $b->[3] || $a->[5] cmp $b->[5] || $a->[1] <=> $b->[1] 
} @lines; 

my ($previous_row, $third, $fifth) = ('') x 3; 
for (@lines) { 
    if ($_->[3] eq $third && $_->[5] eq $fifth) { 
    print $previous_row if $previous_row; 
    print "\t$_->[0]"; 
    $previous_row = ''; 
    } else { 
    $previous_row = $_->[1] . "\t" . $_->[0]; 
    $third = $_->[3]; 
    $fifth = $_->[5]; 
    } 
} 

__DATA__ 
1 L DIELTQSPE H EVQLQESDAELVKPGASVKISCKASGYTFTDHE 
3 A ALQLTQSPSSLSAS B RITLKESGPPLVKPTCS C ELDKWAN 
2 L DIVLTQSPRVT H EVQLQQSGAELVKPGASIKDTY 
5 A ALQLTQSPSSLSAS B RITLKESGPPLVKPTCS C LELDKWASL 
7 L SYELTQPPSVSVSPGSIT H QVQLVQSAKGSGYSFS P YNKRKAFYTTKNIIG 
6 L DIQMTQIPSSLSASLSIC H EVQLQQSGVEVKMSCKASGYTFTS 
9 A MPIMGSSVAVLAIL B DIVMTQSPTVTI C EVQLQQSGRGP 
8 L SYELTQPPSVSVSPGRIT H EVQLVQSGAASGYSFS P NNTRKAFYATGDIIG 
11 L DVVMTQTPLQ H EVKLDESVTVTSSTWPSQSITCNVAHPASSTKVDKKIE 
10 A MPIMGSSVAVLAIL B DIVMTQSPTVTI C EVQLQQSGRGP 
12 A DIVMTQSPDAQYYSTPYSFGQGTKLEIKR 
4 A ALQLTQSPSSLSAS B RITLKESGPPLVKPTCS C ELDKWAG 
+0

+1. Gute, einfache Antwort. Frage: Geht Ihr Code nicht davon aus, dass die zu gruppierenden Zeilen der Reihe nach erscheinen müssen? Wenn dies der Fall ist, dann mag dies eine gute Annahme sein, aber die Frage scheint sich zu fragen. – thb

+1

Unter der Annahme, dass die Zeilen immer in der Eingabe wie gewünscht gruppiert sind, ist dies ein guter Weg, dies zu tun. – Qtax

+0

@thb: Ja, es macht diese Annahme. Wenn das OP antwortet, dass sie in der Eingabe nicht zusammenhängend sind, werde ich den Code anpassen, um ihn zu sortieren. (Ich dachte eigentlich zuerst, dass dies eine Sortierfrage sei, bis ich mir die Probenausgabe genauer angeschaut habe.) –

0

Ihr Ansatz zeigt ein ziemlich solides Verständnis des Perl-Idioms und hat Verdienst, aber immer noch nicht, wie ich es tun würde.

Ich denke, dass Sie mit diesem eine einfachere Zeit haben, wenn Sie etwas anders als Ihre Daten strukturieren: %hash1 so etwas wie

(
    'ALQLTQSPSSLSAS' => { 
     'RITLKESGPPLVKPTCS' => [3, 4, 5], 
     'ABCXYZ' => [93, 95, 96], 
    }, 
    'MPIMGSSVAVLAIL' => { 
     'DIVMTQSPTVTI' => [9, 10], 
    }, 
) 

sein lassen, wo ich ein Datum ABCXYZ hinzugefügt haben, die nicht in Ihrem Beispiel soll zeigen, die Datenstruktur in ihrer Fülle.

1

Beispiel:

use strict; 
use warnings; 

{ ... } 

open my $fh, '<', $file or die "can't open $file: $!"; 

my %hash; 

# read and save it 
while(my $line = <$fh>){ 
    my @line = split /\s+/, $line; 
    my $key = $line[2] . ' ' . $line[4]; 

    $hash{$key} ||= []; 
    push @{$hash{$key}}, $line; 
} 

# remove single elements 
for my $key (keys %hash){ 
    delete $hash{$key} if @{$hash{$key}} < 2; 
} 

print Dumper \%hash; 
+0

+1. Es ist nicht so klassisch wie meine Antwort, aber seit wann ist Perl klassisch? Das sollte funktionieren. Ich mag das. – thb

1

etwas anderen Ansatz:

#!/usr/bin/perl 

use strict; 
use warnings; 

my %lines; # hash with 3rd and 5th elements as key 
my %first_line_per_group; # stores in which line a group appeared first 

while(my $line = <>) { 
    # remove line break 
    chomp $line; 

    # retrieve elements form line 
    my @elements = split /\s+/, $line; 

    # ignore invalid lines 
    next if @elements < 5; 

    # build key from elements 3 and 5 (array 0-based!) 
    my $key = $elements[2] . " " . $elements[4]; 

    if(! $lines{key}) { 
     $first_line_per_group{$key} = $elements[0]; 
    } 

    push @{ $lines{$key} }, $line; 
} 


# output 
for my $key (keys %lines) { 
    print $first_line_per_group{$key} . ":\n"; 

    print " $_\n" for @{ $lines{$key} }; 
} 
+0

+1. Siehe jedoch meinen Kommentar zu @Qtax. – thb

0

Sie sollten die 3-Argument-Form von open() verwenden und das Lesen der Daten vereinfachen:

open my $fh, '<', $file 
    or die "Cannot open '$file': $!\n"; 

chomp(my @rows = <$fh>); 
@rows = map {[split]} @rows; 

close $fh; 

Um die Zeilen zu gruppieren, können Sie einen Hash mit dem 3. und 5. Feld verwenden, die als Schlüssel verkettet sind. Bearbeiten: Sie müssen ein Trennzeichen hinzufügen, um ungültige Ergebnisse zu eliminieren, "wenn verschiedene Linien die gleiche Verkettung erzeugen" (Qtax). Zusätzliche Daten, beispielsweise die Anzahl der einzelnen Datenzeilen, können als Hash-Wert gespeichert werden. Hier werden die Zeile des Feldern gespeichert:

my %groups; 
for (@rows) { 
    push @{ $groups{$_->[2] . ' ' . $_->[4]} }, $_ 
     if @$_ >= 4; 
} 

aussortieren einzelnen Elemente:

@{ $groups{$_} } < 2 && delete $groups{$_} 
    for keys %groups; 

Greets, Matthias

+0

Beachten Sie, dass Sie nur '$ _-> [2] verwenden. $ _-> [4] 'da der Schlüssel ungültige Ergebnisse liefern kann, wenn verschiedene Zeilen die gleiche Verkettung dieser Werte erzeugen. – Qtax

+0

Ah! Daran habe ich nicht gedacht. Sie müssen also ein Trennzeichen einfügen (wie in der Antwort von Halo). – Matthias