按内部哈希值对哈希数组哈希进行排序

时间:2013-12-19 14:14:31

标签: perl sorting hash

我很难缠绕这个头,而且我确信这只是今天变得密集的一个例子。我有一个与此类似的数据结构:

{
  PDGFRA => [
    { "p.N659K" => 22 },
    { "p.D842Y" => 11 },
    { "p.I843_S847>T" => 9 },
    { "p.D842_H845del" => 35 },
    { "p.I843_D846delIMHD" => 24 },
    { "p.I843_D846del" => 21 },
    { "p.D842V" => 457 },
    { "p.N659Y" => 5 },
    { "p.M844_S847del" => 7 },
    { "p.S566_E571>K" => 8 },
    { "p.S566_E571>R" => 50 },
    { "p.V561D" => 54 },
  ],
}

我想打印出结果,通过哈希值反向排序(从大到小),最终我最终会得到类似的结果

PDGFRA    p.D842V    457
PDGFRA    p.V561D    54
PDGFRA    p.S566_E571>R    50
PDGFRA    p.D842_H845del    35
.
.
.
etc.

我按照自己的意愿打印数据结构没有问题,但我似乎无法弄清楚如何在打印之前对数据进行排序。我试过这样的事情:

for my $gene ( sort keys %matches ) {
    for my $var ( sort { $a->{$var} <=> $b->{$var} } @{$matches{$gene}} {
        print "$var\n";
    }
 }

但是,无论我使用$var还是$_,它似乎都不起作用,抱怨未定义'$ var',并且未初始化'$ _'。我也试过(真的很可怜!)使用Schwarzian变换,但我认为我甚至不接近这个:

for my $gene ( sort keys %matches ) {
    my @sorted = 
        map { $_->[0] } 
        sort { $a->[1] <=> $b->[1] } 
        map { [ $_, $matches{$gene}->{$_} ] } @{$matches{$gene}};
}

有人会介意我指出正确的方向来按内部哈希值对散列或散列数组进行排序吗?

4 个答案:

答案 0 :(得分:3)

这是另一种选择。这个工作方法是将 indices 列表排序到数组@sorted中,这样就可以迭代数组切片@$list[@sorted]

你受到了数据格式的阻碍,而且只要在给定单元素哈希的情况下提取一对值就太麻烦了。

我希望这会有所帮助。

use strict;
use warnings;

my %matches = (
  PDGFRA => [
    { "p.N659K"            =>  22 },
    { "p.D842Y"            =>  11 },
    { "p.I843_S847>T"      =>   9 },
    { "p.D842_H845del"     =>  35 },
    { "p.I843_D846delIMHD" =>  24 },
    { "p.I843_D846del"     =>  21 },
    { "p.D842V"            => 457 },
    { "p.N659Y"            =>   5 },
    { "p.M844_S847del"     =>   7 },
    { "p.S566_E571>K"      =>   8 },
    { "p.S566_E571>R"      =>  50 },
    { "p.V561D"            =>  54 },
  ],
);

while (my ($key, $list) = each %matches) {

  my @sorted = sort {
    my ($ka, $va) = %{ $list->[$b] };
    my ($kb, $vb) = %{ $list->[$a] };
    $va <=> $vb;
  } 0 .. $#$list;

  for my $item (@$list[@sorted]) {
    printf "%s   %s     %d\n", $key, %$item;
  }
}

<强>输出

PDGFRA   p.D842V     457
PDGFRA   p.V561D     54
PDGFRA   p.S566_E571>R     50
PDGFRA   p.D842_H845del     35
PDGFRA   p.I843_D846delIMHD     24
PDGFRA   p.N659K     22
PDGFRA   p.I843_D846del     21
PDGFRA   p.D842Y     11
PDGFRA   p.I843_S847>T     9
PDGFRA   p.S566_E571>K     8
PDGFRA   p.M844_S847del     7
PDGFRA   p.N659Y     5

<强>更新

在我对David W的回答的评论中,我建议一个数据结构只是一个双元素数组的数组,而不是单元素哈希数组。

这就是看起来的样子。输出与上面的代码相同

use strict;
use warnings;

my %matches = (
  PDGFRA => [
    [ "p.N659K",             22 ],
    [ "p.D842Y",             11 ],
    [ "p.I843_S847>T",        9 ],
    [ "p.D842_H845del",      35 ],
    [ "p.I843_D846delIMHD",  24 ],
    [ "p.I843_D846del",      21 ],
    [ "p.D842V",            457 ],
    [ "p.N659Y",              5 ],
    [ "p.M844_S847del",       7 ],
    [ "p.S566_E571>K",        8 ],
    [ "p.S566_E571>R",       50 ],
    [ "p.V561D",             54 ],
  ],
);

while (my ($key, $list) = each %matches) {

  my @sorted = sort { $list->[$b][1] <=> $list->[$a][1] } 0 .. $#$list;

  for my $item (@$list[@sorted]) {
    printf "%s   %s     %d\n", $key, @$item;
  }
}

答案 1 :(得分:2)

您已经接受了答案,但我想对您的数据结构发表评论。你有:

  • 哈希
  • 该哈希包含一个数组。
  • 这些数组包含一个只包含一个键的哈希值。

为什么包含哈希的数组只有一个键?为什么不简单地摆脱阵列?

$VAR1 = {
      'PDGFRA' =>  {
                      'p.N659K' => 22,
                      'p.D842Y' => 11,
                      'p.I843_S847>T' => 9,
                      'p.D842_H845del' => 35,
                      'p.I843_D846delIMHD' => 24,
                      'p.I843_D846del' => 21,
                      'p.D842V' => 457,
                      'p.N659Y' => 5,
                      'p.M844_S847del' => 7,
                      'p.S566_E571>K' => 8,
                      'p.S566_E571>R' => 50,
                      'p.V561D' => 54,
     };

这将大大简化您的结构,以便更容易找到最内部哈希中包含的值。您可以直接访问密钥。

如果问题是某些散列键可能重复,则可以使该散列键指向值数组:

$VAR1 = {
      'PDGFRA' =>  {
                      'p.N659K' => 22,
                      'p.D842Y' => 11,
                      'p.I843_S847>T' => [
                                            6,
                                            9
                                          ],
                      'p.D842_H845del' => 35,
                      'p.I843_D846delIMHD' => 24,
                      'p.I843_D846del' => 21,
                       ...

请注意,p.I843_S847同时包含69。您可以简化并使内部哈希的每个值成为对数组的引用,并且这些数组中的99%可能包含单个值,或者您可以使用ref命令检测内容是标量还是引用到一个数组。无论哪种方式,您仍然可以获得更快的查找速度,并且更容易访问该哈希中的键,因此您可以对它们进行排序。

因为您熟悉在Perl中使用复杂的数据结构,所以您还应该了解Object Oriented Perl的工作原理。这将使处理这些结构变得更加容易,并且还将帮助您进行开发,因为它为您提供了与这些复杂结构相关的简洁方法。

答案 2 :(得分:1)

假设上面的$VARData::Dumper::Dumper(\%matches) ...

如果您不想让您的数据结构更好......

for my $gene ( sort keys %matches ) {
    for my $hash ( sort { 
                      my ($akey) = keys %$a;
                      my ($bkey) = keys %$b;
                      $a->{$akey} <=> $b->{$bkey} 
                   } @{$matches{$gene}} ) {
        my ($key) = keys %$hash;
        print "$key => $hash->{$key}\n";
    }
}

按值(例如:12)排序而不是键(例如:'p.I843_D846del')。我想你自己使用了数字比较,你想要按数值排序; - )

编辑:内环的固定体。

编辑2:

我看到您尝试了Schwartzian Transform ...如果按原样保留数据结构,这可能是一种更有效的解决方案......如下所示:

for my $gene ( sort keys %matches ) {
    print "$_->[0] => $_->[1]\n" for                     # print both key and value
        sort { $a->[1] <=> $b->[1] }                     # sort by value (e.g: 35)
        map { my ($key) = keys %$_; [$key, $_->{$key}] } # e.g:['p.D842_H845del' ,35]
        @{$matches{$gene}};
}  

但相反,我只是修复了数据结构。

可能只是将'key'(例如:'p.I843_D846del')和'value'(例如12)这两个值都赋予它们并给它们一致的键名。

答案 3 :(得分:0)

您的数据结构似乎有不必要的深度。但这是一个Schwartzian Transform版本,可以根据需要对其进行排序。

for my $gene (sort keys %matches) {
    my @sorted = map  { {@$_} }                # Back to hash.
                 sort { $b->[1] <=> $a->[1] }  # Sort.
                 map  { [each %$_] }           # Unpack the 1-key hash.
                 @{$matches{$gene}};
}