【问题标题】:perl compare elements of arrays and groupingperl比较数组元素和分组
【发布时间】:2012-06-09 12:37:02
【问题描述】:

我带着另一个问题回来了。我有一个数据列表:

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 个元素和第 5 个元素,如果它们具有相同的第 3 个和第 5 个元素,则将它们分组。 例如,使用上面的数据,结果将是:

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

仅供参考,在实际数据中,第 3、5、7 个元素很长。我已经把它们剪下来看看整体。

这就是我所做的,我知道这很笨拙,但是作为初学者,我正在尽力而为。 问题是它只显示第一组“相同”组。 你能告诉我哪里出了问题和/或其他解决这个问题的漂亮方法吗?

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;

【问题讨论】:

  • 谢谢大家。你所有的 cmets 和代码对我真的很有帮助。感谢您更正我的“模拟”数据并考虑进一步的步骤。 :-)

标签: arrays perl compare


【解决方案1】:

您似乎把它复杂化了一点,不过这对于初学者来说很常见。更多地考虑如何手动执行此操作:

  • 查看每一行。
  • 查看第三个和第五个字段是否与上一行相同。
  • 如果有,请打印出来。

循环和所有这些都是完全不必要的:

#!/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:针对 cme​​ts,这是第二个版本,它不假定应分组的行是连续的:

#!/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:是的,它确实做出了这样的假设。如果 OP 响应它们在输入中不连续,我将修改代码以包括对其进行排序。 (实际上,在我更仔细地查看示例输出之前,我最初认为这是一个排序问题。)
  • 哇……这就是高手思维方式和初学者思维方式的差距??!!!只希望我能尽快达到水平。非常感谢~!
【解决方案2】:

略有不同的方法:

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

【讨论】:

    【解决方案3】:

    例子:

    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 从什么时候开始经典?这应该有效。我喜欢它。
    【解决方案4】:

    您的方法显示出对 Perl 习语的相当扎实的掌握并且有优点,但仍然不是我会这样做的方式。

    我认为,如果您的数据结构稍有不同,您会更轻松:让%hash1 类似于

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

    我在其中添加了一个数据 ABCXYZ,它不在您的示例中以完整地显示数据结构。

    【讨论】:

      【解决方案5】:

      您应该使用 open() 的 3 参数形式,并且可以简化数据的读取:

      open my $fh, '<', $file
          or die "Cannot open '$file': $!\n";
      
      chomp(my @rows = <$fh>);
      @rows = map {[split]} @rows;
      
      close $fh;
      

      要对行进行分组,您可以使用将第 3 和第 5 个字段连接为键的散列。编辑:您必须添加一个分隔字符以消除“如果不同的行产生相同的连接”(Qtax)的无效结果。附加数据,例如,单个数据行的数量,可以存储为散列值。这里存储了行的字段:

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

      对单个元素进行排序:

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

      问候, 马蒂亚斯

      【讨论】:

      • 请注意,如果不同的行产生相同的值串联,则仅使用 $_-&gt;[2] . $_-&gt;[4] 作为键会产生无效结果。
      • 啊!没想到。所以你必须插入一个分隔符(就像在光环的答案中一样)。
      猜你喜欢
      • 1970-01-01
      • 2018-06-14
      • 2016-03-25
      • 1970-01-01
      • 2010-11-07
      • 1970-01-01
      • 2015-07-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多