为什么该语句被视为字符串而非结果?

5
我正在尝试对一大批字符串(蛋白质序列)执行基于组成的过滤。我编写了三个子程序来处理它,但是我遇到了两个问题-一个小问题和一个大问题。 小问题是当我使用List::MoreUtils 'pairwise'时,会收到有关仅使用$a$b一次且未初始化它们的警告。 但我认为我已正确调用此方法(基于CPAN的条目和一些来自网络的示例)。 主要问题是出现错误:"Can't use string ("17/32") as HASH ref while "strict refs" in use..."。这似乎只会在&comp中的foreach循环将哈希值作为字符串而不是评估除法运算时发生。 我确定我犯了初学者的错误,但找不到答案。即使是上周三我第一次看Perl代码...
use List::Util;
use List::MoreUtils;
my @alphabet = (
 'A', 'R', 'N', 'D', 'C', 'Q', 'E', 'G', 'H', 'I',
 'L', 'K', 'M', 'F', 'P', 'S', 'T', 'W', 'Y', 'V'
);
my $gapchr = '-';
# Takes a sequence and returns letter => occurrence count pairs as hash.
sub getcounts {
 my %counts = ();
 foreach my $chr (@alphabet) {
  $counts{$chr} = ( $_[0] =~ tr/$chr/$chr/ );
 }
 $counts{'gap'} = ( $_[0] =~ tr/$gapchr/$gapchr/ );
 return %counts;
}

# Takes a sequence and returns letter => fractional composition pairs as a hash.
sub comp {
 my %comp = getcounts( $_[0] );
 foreach my $chr (@alphabet) {
  $comp{$chr} = $comp{$chr} / ( length( $_[0] ) - $comp{'gap'} );
 }
 return %comp;
}

# Takes two sequences and returns a measure of the composition difference between them, as a scalar.
# Originally all on one line but it was unreadable.

sub dcomp {
 my @dcomp = pairwise { $a - $b } @{ values( %{ comp( $_[0] ) } ) }, @{ values( %{ comp( $_[1] ) } ) };
 @dcomp = apply { $_ ** 2 } @dcomp;
 my $dcomp = sqrt( sum( 0, @dcomp ) ) / 20;
 return $dcomp;
}

非常感谢您的回答和建议!

请参见https://dev59.com/_HE95IYBdhLWcg3wkegf#2261957,以获取更多关于这个问题发生的原因和方式的解释。+1 - DVK
那个哈希引用错误在哪一行? - DVK
对于这个小问题,请查看https://dev59.com/jHI_5IYBdhLWcg3wMf5_。 - FMc
哈希引用错误出现在&comp中,$comp {$chr} = $comp {$chr} /(length($ _ [0])- $comp {'gap'}) - reve_etrange
4个回答

4
你的代码中有几个错误。首先,请注意来自perldoc perlop的说明:

由于转换表在编译时构建,因此SEARCHLIST和REPLACEMENTLIST都不受双引号插值的影响

所以你的计数方法是不正确的。我认为你误用了pairwise。很难评估什么构成了正确的使用,因为你没有给出一些简单输入应该得到的输出示例。
无论如何,我会重写这个脚本(其中有一些调试语句):
#!/usr/bin/perl

use List::AllUtils qw( sum );
use YAML;

our ($a, $b);
my @alphabet = ('A' .. 'Z');
my $gap = '-';

my $seq1 = 'ABCD-EFGH--MNOP';
my $seq2 = 'EFGH-ZZZH-KLMN';

print composition_difference($seq1, $seq2);

sub getcounts {
    my ($seq) = @_;
    my %counts;
    my $pattern = join '|', @alphabet, $gap;
    $counts{$1} ++ while $seq =~ /($pattern)/g;
    warn Dump \%counts;
    return \%counts;
}

sub fractional_composition_pairs {
    my ($seq) = @_;
    my $comp = getcounts( $seq );
    my $denom = length $seq - $comp->{$gap};
    $comp->{$_} /= $denom for @alphabet;
    warn Dump $comp;
    return $comp;
}

sub composition_difference {
    # I think your use of pairwise in the original script
    # is very buggy unless every sequence always contains
    # all the letters in the alphabet and the gap character.
    # Is the gap character supposed to factor in the computations here?

    my ($comp1, $comp2) = map { fractional_composition_pairs($_) } @_;
    my %union;
    ++ $union{$_} for (keys %$comp1, keys %$comp2);

    my $dcomp;
    {
        no warnings 'uninitialized';
        $dcomp = sum map {
            ($comp1->{$_} - $comp2->{$_}) ** 2
        } keys %union;
    }

    return sqrt( $dcomp ) / 20; # where did 20 come from?
}

不涉及生物学(似乎不是这个地方!),序列间的间隙并不构成序列“组成”的差异,因为它们不是字母表中的一个字母(20种氨基酸之一)。20是将$dcomp从0标准化到1的(任意)值。我现在明白了map()...谢谢! - reve_etrange

2
%{ $foo }会将$foo视为哈希引用并对其进行解引用;同样,@{}将对数组引用进行解引用。由于comp作为列表返回哈希(在函数间传递哈希时,哈希变成了列表),而不是哈希引用,所以%{}是错误的。您可以潜在地省略%{},但values是一种特殊形式,需要一个哈希,而不是作为列表传递的哈希。要将comp的结果传递给valuescomp需要返回一个哈希引用,然后对其进行解引用。
您的dcomp还有另一个问题,即values的顺序(如文档所述)“以似乎随机的顺序返回”,因此传递给pairwise块的值不一定是相同字符的值。您可以使用哈希切片代替values。现在我们回到了comp返回哈希(作为列表)的情况。
sub dcomp {
    my %ahisto = comp($_[0]);
    my %bhisto = comp($_[1]);
    my @keys = uniq keys %ahisto, keys %bhisto;
    my @dcomp = pairwise { $a - $b } , @ahisto{@keys}, @bhisto{@keys};
    @dcomp = apply { $_ ** 2 } @dcomp;
    my $dcomp = sqrt( sum( 0, @dcomp ) ) / 20;
    return $dcomp;
}

如果一个字符只出现在$_[0]$_[1]中的一个中,这个问题就没有解决。

uniq留给读者作为练习。


我认为散列切片必须用@{ [ ] }括起来? 现在我知道values()的危险了,谢谢。 - reve_etrange

2

我看了你提供的代码,这是我写的方式。虽然我不知道它是否能按照你想要的方式工作。

use strict;
use warnings;
our( $a, $b );

use List::Util;
use List::MoreUtils;

my @alphabet = split '', 'ARNDCQEGHILKMFPSTWYV';
my $gapchr = '-';
# Takes a sequence and returns letter => occurrence count pairs as hash.
sub getcounts {
  my( $sequence ) = @_;
  my %counts;
  for my $chr (@alphabet) {
    $counts{$chr} = () = $sequence =~ /($chr)/g;
    # () = forces list context
  }
  $counts{'gap'} = () = $sequence =~ /($gapchr)/g;
  return %counts if wantarray; # list context
  return \%counts; # scalar context
  # which is what happens inside of %{  }
}

# Takes a sequence and returns letter => fractional composition pairs as a hash
sub comp {
  my( $sequence ) = @_;
  my %counts = getcounts( $sequence );
  my %comp;
  for my $chr (@alphabet) {
    $comp{$chr} = $comp{$chr} / ( length( $sequence ) - $counts{'gap'} );
  }
  return %comp if wantarray; # list context
  return \%comp; # scalar context
}

# Takes two sequences and returns a measure of the composition difference
#   between them, as a scalar.
sub dcomp {
  my( $seq1, $seq2 ) = @_;
  my @dcomp = pairwise { $a - $b }
    @{[ values( %{ comp( $seq1 ) } ) ]},
    @{[ values( %{ comp( $seq2 ) } ) ]};
  # @{[ ]} makes a list into an array reference, then dereferences it.
  # values always returns a list
  # a list, or array in scalar context, returns the number of elements
  # ${  } @{  } and %{  } forces their contents into scalar context

  @dcomp = apply { $_ ** 2 } @dcomp;
  my $dcomp = sqrt( sum( 0, @dcomp ) ) / 20;
  return $dcomp;
}

你需要了解的最重要的事情之一是标量、列表和空上下文之间的区别。这是因为所有东西在不同的上下文中表现不同。


啊,我了解关于上下文的情况。'if wantarray'是一个很棒的技巧。 - reve_etrange

1

关于小问题

这是正常的问题,与一些 List::UtilList::MoreUtils 模块有关。

消除警告的一种方法是提前声明那些 特殊变量,如下所示:

our ($a, $b);

另一种方法是在 pairwise 前加上:
no warnings 'once';

有关 $a 和 $b 的更多信息,请参阅 perlvar

/I3az/


你会在子块还是全局命名空间中进行声明呢(我对正确术语不是很熟悉)? 如果我只使用该方法一次,那么似乎应该在子块中声明;如果我在多个地方使用它,则应该在全局命名空间中声明。 - reve_etrange
在这种情况下,在程序顶部放置 our ($a, $b) 是可以的。$a 和 $b 是特殊的,因此除了 sort 和像 List::Util 这样的模块之外,不应 用于其他任何东西。问题是 warnings once 没有考虑到 $a 和 $b 特殊变量,除了 sort - draegtun

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