Perl比较数组元素并进行分组

5

我又回来问问题了。我有一份数据列表:

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

我将对每行的第三个和第五个元素进行比较,如果它们相同,则将它们分组。例如,对于上面的数据,结果如下:

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

提醒:实际数据中,第三个、第五个和第七个元素非常长。我将它们截断以便查看全部内容。

这是我所做的,我知道它非常冗长,但作为一个初学者,我正在尽力而为。问题在于它仅显示了“相同”组的第一组。你能告诉我哪里出错了或其他更好的解决方法吗?

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;

谢谢大家。你们所有的评论和代码对我来说都非常有帮助。感谢你们甚至纠正了我的“模拟”数据并考虑了进一步的步骤。 :-) - Krista
5个回答

2
您似乎把这个问题复杂化了,但这对于初学者来说是很常见的。请想想您如何手动完成以下操作:
- 查看每一行。 - 查看第三个和第五个字段是否与前一行相同。 - 如果相同,则打印它们。
循环等操作完全没有必要。
#!/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

请注意,我稍微更改了第10行的内容,以便它的第三个字段与第9行匹配,以便输出与指定的相同的组。

编辑:由于复制/粘贴错误,一行代码被重复了。

编辑2:针对评论,这是第二个版本,它不假设应该分组的行是连续的:

#!/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

+1. 很好,简单明了的回答。不过,问题是:你的代码是否假定要分组的行必须按顺序出现?如果是这样,那么这可能是一个很好的假设,但这个问题似乎值得问一下。 - thb
1
假设输入的行总是按照所需分组,这是一个很好的方法。 - Qtax
@thb:是的,它确实做出了这个假设。如果OP回复说输入不是连续的,我会修改代码以包括排序。 (实际上,我最初认为这是一个排序问题,直到我更仔细地查看了示例输出。) - Dave Sherohman
哇...这就是专家思维和初学者之间的差距吗?!只希望我能尽快达到那个水平。非常感谢! - Krista

1

例子:

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;

+1. 这并不像我的答案那样经典,但自从 Perl 出现以来,它就不是经典的了。这应该可以工作。我喜欢它。 - thb

1
略有不同的方法:
#!/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

你的方法展示了对Perl习惯用法的很好掌握,并且有其优点,但仍不是我会采用的方式。

我认为如果你稍微改变一下数据结构,这个问题会更容易解决:让%hash1成为类似于以下结构:

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

我添加了一个数据 ABCXYZ,这个数据不在你的示例中,以展示数据结构的完整性。


0

你应该使用open()的三个参数形式,并且可以简化读取数据的过程:

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

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

close $fh;

为了对行进行分组,您可以使用哈希表,将第三个和第五个字段连接在一起作为键。编辑:您必须添加一个分隔符来消除无效结果“如果不同的行产生相同的连接”(Qtax)。例如,单个数据行的编号等其他数据可以存储为哈希值。这里,行的字段被存储:

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

整理单个元素:

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

你好,Matthias


请注意,如果不同的行产生相同的这些值的连接,则仅使用$_ -> [2] . $_ -> [4]作为键可能会导致无效结果。 - Qtax
啊!没想到那个。所以你必须插入一个分隔符(就像halo的答案中一样)。 - Matthias

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接