【问题标题】:Sorting a Hash of Array of Hashes by Internal Hash Value按内部散列值对散列数组的散列进行排序
【发布时间】:2014-01-08 03:42:48
【问题描述】:

我很难理解这一点,我敢肯定这只是今天变得密集的一个例子。我有一个类似这样的数据结构:

{
  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' 没有定义,'$_' 没有初始化。我也尝试(真的很可怜!)使用施瓦兹变换,但我认为我什至没有接近这个:

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

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

【问题讨论】:

  • 数组 ref 中的所有 hash-refs 只会包含 1 个键?但它是每个 hash-ref 的不同键?这只是一个奇怪的数据结构,不太利于遍历(或排序)......虽然很容易处理。

标签: perl sorting hash


【解决方案1】:

这是另一种选择。这通过将 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;
  }
}

【讨论】:

  • 非常有帮助且内容丰富!也感谢您编辑我的 OP;我应该考虑使用 Data::Dump 而不是 Data::Dumper!
  • @drmrgd:不客气。 Data::Dump 通常更优越,但 Data::Dumper 自 005 版以来一直是随 Perl 5 一起分发的核心模块,因此它的立足点要大得多。另外,我对我的更新代码中的一个错误表示歉意:我有两次 $b 应该是 $b$a
【解决方案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 的工作原理。这将使处理这些结构变得更加容易,并且还将帮助您进行开发,因为它为您提供了一种与这些复杂结构相关联的简洁方式。

【讨论】:

  • 不错的替代方案总结。我猜哈希永远不会通过键访问,所以它们也可能是二元素数组。
  • 感谢两位非常有见地的回答!我认为你们两个(实际上都是你们的!)都是正确的,我过于复杂了我的数据结构,试图不仅适合所有情况,而且“一刀切”以预期下游数据的使用。我想我将把上述数据结构重建为 David W 提出的更简单的案例。在我展示的内容中,我并没有真正使用哈希键。但是,我将在脚本中将它们用于其他目的。所以,我认为 HoH 肯定会更好。
【解决方案3】:

假设上面的$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}};
}  

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

可能只是将“键”(例如:“p.I843_D846del”)和“值”(例如:12)都设置为值,并为它们提供一致的键名。

【讨论】:

  • 是的,我认为您可能是对的 - 我可能会考虑使用不同的数据结构。我使用键来获取计数数据(这是来自一个更大、更复杂的数据集)。但是这种绝对不漂亮。我将您的代码中的排序顺序更改为从最大到最小,并且在@{$matches{$gene}} 之前缺少一个括号,但是代码在其他方面工作得很好!不确定是否有更好的方法来处理这些数据,但我认为这现在应该可以正常工作。非常感谢!
  • 刚看到ST方法。这实际上还是要好一些。我想我在构建原始结构以传递给转换的排序部分时看到了哪里出错了。这非常有帮助;再次感谢!我还忘了提到第一种方法中的另一个错字:我们需要取消引用哈希,所以该行应该是print "$key =&gt; $hash-&gt;{$key}\n"'
  • 啊,是的。固定的。 (实际上我应该在发布之前尝试代码!)
【解决方案4】:

您的数据结构似乎有不必要的深度。但这里有一个 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}};
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-05-19
    • 2010-11-02
    • 2014-05-07
    • 2015-01-26
    • 2012-10-09
    • 1970-01-01
    • 1970-01-01
    • 2014-07-03
    相关资源
    最近更新 更多