【问题标题】:Sorting a hash of hashes by the inner hash's values按内部哈希值对哈希进行排序
【发布时间】:2015-08-14 02:55:35
【问题描述】:

我有一个哈希值

my %change;

while ( <DATA> ) {
    chomp;
    my ($gene, $condition, $change) = split;
    $change{$gene}{$condition} = $change;
}

print Dumper \%change;

__DATA__
gene1   condition1  10
gene2   condition1  0.5
gene3   condition1  1.5
gene1   condition2  2
gene2   condition2  13.5
gene3   condition2  0.25

我想按值排序:

gene2   condition2  13.5
gene1   condition1  10
gene1   condition2  2
gene3   condition1  1.5
gene2   condition1  0.5
gene3   condition2  0.25

我正在使用:

for my $g (keys %change){
    for my $con (keys $change{$g}){
        for my $ch (sort { $change{$g}{$a} <=> $change{$g}{$b} } keys $change{$g}{$con} ) {
            print "$g\t$con\t$ch\n";
        }

    }
}

但这不起作用,并产生错误

引用键的参数类型必须是 untitled.pl 第 23 行第 6 行的 unblessed hashref 或 arrayref。

第 23 行是

for my $ch (sort { $change{$g}{$a} <=> $change{$g}{$b} } keys $change{$g}{$con}){

谁能指出我正确的方向?

【问题讨论】:

  • 该错误意味着包含在 - 我假设 - $change{$g} 中的值不是 hashref 或 arrayref。显示您正在使用的数据结构、分配方式或等效的可运行代码。
  • 是的,在后来的 Perl 中,它会警告 hashref 上的键是实验性的。但这不是唯一的问题。如果你解决了这个问题,它会抱怨更多。
  • 第 6 行是哪一行?
  • @fugu 如果您唯一关心的是根据文本每行中的数值进行排序,那么最好在收集数据时做好准备,而不是事后转换哈希。跨度>
  • 您可能对纯 bash 解决方案感兴趣:stackoverflow.com/q/17430470/725418

标签: perl sorting hash


【解决方案1】:

我认为您不太可能需要像这样的哈希结构中的数据。当然,出于此任务的目的,您最好使用数组数组

use strict;
use warnings;

my @change;

while ( <DATA> ) {
    push @change, [ split ];
}

print "@$_\n" for sort { $b->[2] <=> $a->[2] } @change;


__DATA__
gene1   condition1  10
gene2   condition1  0.5
gene3   condition1  1.5
gene1   condition2  2
gene2   condition2  13.5
gene3   condition2  0.25

输出

gene2 condition2 13.5
gene1 condition1 10
gene1 condition2 2
gene3 condition1 1.5
gene2 condition1 0.5
gene3 condition2 0.25

如果您解释了您需要对数据进行何种访问,那么我相信会有更好的方法。例如,我建议将基因或条件 ID 映射到使用该基因的数组元素列表的 %gene%condition 哈希。然后,当您知道基因或条件时,您就可以访问数据

【讨论】:

    【解决方案2】:

    正如我在 cmets 中提到的,最简单的解决方案不是先将文本输入解析为哈希,然后对哈希进行排序,而是将数据收集成更合适的形式并在那里排序。

    另外,请注意,在迭代值时不能进行排序。您需要编译一个列表,并一次性对该列表进行排序,因为sort 本身就是一种迭代器。

    我首先展示了我为给定输入选择的方法,然后展示了如何对哈希进行排序。

    use strict;
    use warnings;
    
    my %change;
    my @sort;
    while(<DATA>) {
        chomp;
        my ($gene, $condition, $change) = split;
        $change{$gene}{$condition} = $change;
        push @sort, [ $change, $_ ];
    }
    
    @sort = sort { $a->[0] <=> $b->[0] } @sort;
    say $_->[1] for @sort;
    
    # Using the hash:
    
    my @values;
    for my $gene (keys %change) {
        for my $con (keys %{ $change{$gene} }) {
            my $num = $change{$gene}{$con};
            push @values, [ $num, "$gene\t$con\t$num" ];
        }
    }
    @values = sort { $a->[0] <=> $b->[0] } @values;
    say $_->[1] for @values;
    
    __DATA__
    gene1   condition1  10
    gene2   condition1  0.5
    gene3   condition1  1.5
    gene1   condition2  2
    gene2   condition2  13.5
    gene3   condition2  0.25
    

    如您所见,我正在使用一种缓存来更轻松地访问该值。例如,push @sort, [ $change, $_ ] 存储一个数组 ref 和数值,以及来自输入的原始字符串。然后可以在排序时使用$a-&gt;[0] 访问这些值,在打印时使用$_-&gt;[1] 访问这些值。

    我发现这种方法简单而可靠。虽然如果您的输入文件非常大,由于数据的重复,它可能会导致一些内存问题。但在现代系统上,任何小于千兆字节的数据都应该没问题。

    【讨论】:

      【解决方案3】:

      您可以展平散列结构,然后按数值(数组数组中的最后一个元素)进行数字排序

      my $VAR1 = {
            'gene1' => {
                         'condition1' => '10',
                         'condition2' => '2'
                       },
            'gene2' => {
                         'condition1' => '0.5',
                         'condition2' => '13.5'
                       },
            'gene3' => {
                         'condition1' => '1.5',
                         'condition2' => '0.25'
                       }
          };
      
      my @sorted = sort {
          $b->[2] <=> $a->[2]
        }
        map {
          my $k = $_;
          my $h = $VAR1->{$k};
          map [ $k, $_, $h->{$_} ], keys %$h;
        }
        keys %$VAR1;
      
      print "@$_\n" for @sorted;
      

      输出

      gene2 condition2 13.5
      gene1 condition1 10
      gene1 condition2 2
      gene3 condition1 1.5
      gene2 condition1 0.5
      gene3 condition2 0.25
      

      使用foreach 代替map

      my @arr;
      for my $k (keys %$VAR1) {
        my $h = $VAR1->{$k};
        for (keys %$h) {
          push @arr, [ $k, $_, $h->{$_} ];
        }
      }
      my @sorted = sort { $b->[2] <=> $a->[2] } @arr;
      

      【讨论】:

      • 感谢您的回答,但有没有办法在不使用map 的情况下做到这一点(如果可能,我会尽量避免)
      • @fugu 你不应该害怕map。它很像forsort,只是遍历列表的另一种方式。
      • 我仍然对map 保持警惕,因为这是一条难以理解代码的道路。所以在试图解释事情时倾向于避免它。但如果使用得当,它也很酷。
      • @Sobrique 这是一个工具,就像任何工具一样。使用my @data = map ... &lt;input&gt; 比使用常规循环和push 更简单。在进行 Schwartzian 变换时也很重要。
      • 我不否认它是一个强大的工具。然而,我也认为这是创建perl 著名的“只写”代码类型的好方法——如果你不小心如何使用它的话。因此,在初学者正确“理解”它之前,最好不要落入初学者手中 - 虽然您可以做出一些非常简洁的解决方案,但如果阅读它的人头疼尝试,我认为它们不会“增加价值”了解发生了什么。
      【解决方案4】:

      你只有两个哈希值,所以

      • %change 是一个哈希值。
      • $change{$g} 是对哈希的引用
      • %{ $change{$g} } 是一个哈希值。
      • $change{$g}{$con} 是一个数字。
      • 正如报告的那样,%{ $change{$g}{$con} } 是一个错误。

      解决办法是……嗯,没有办法解决。你采取的方法不能用来解决你的问题。


      您无法对哈希进行排序。您可以对哈希的键进行排序,但这不是您想要的。您需要对密钥对进行排序。因此,首先,您必须创建这些密钥对。

      map {
         my $outer_key = $_;
         map {
            my $inner_key = $_;
            [ $outer_key, $inner_key ]
         } keys %{ $change{$_} }
      } keys(%change)
      

      这会创建

      [
         [ 'gene1', 'condition1' ],
         [ 'gene1', 'condition2' ],
         [ 'gene2', 'condition1' ],
         [ 'gene2', 'condition2' ],
         [ 'gene3', 'condition1' ],
         [ 'gene3', 'condition2' ],
      ]
      

      当我们对它们进行排序时

      sort { $change{ $a->[0] }{ $a->[1] } <=> $change{ $b->[0] }{ $b->[1] }
      

      大家一起:

      for (
         sort { $change{ $a->[0] }{ $a->[1] } <=> $change{ $b->[0] }{ $b->[1] }
         map {
            my $gene = $_;
            map {
               my $con = $_;
               [ $gene, $con ]
            } keys %{ $change{$_} }
         } keys(%change)
      ) {
         my ($gene, $con) = @$_;
         print("$g\t$con\t$change{$gene}{$con}\n");
      }
      

      但是,如果我们改为创建以下扁平结构呢?

      [
         [ 'gene1', 'condition1', 10    ],
         [ 'gene1', 'condition2',  2    ],
         [ 'gene2', 'condition1',  0.5  ],
         [ 'gene2', 'condition2', 13.5  ],
         [ 'gene3', 'condition1',  1.5  ],
         [ 'gene3', 'condition2',  0.25 ],
      ]
      

      这将使我们能够简化一些。

      for (
         sort { $a->[2] <=> $b->[2] }
         map {
            my $gene = $_;
            map {
               my $con = $_;
               [ $gene, $con, $change{$gene}{$con} ]
            } keys %{ $change{$_} }
         } keys(%change)
      ) {
         my ($gene, $con, $ch) = @$_;
         print("$g\t$con\t$ch\n");
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-20
        • 2011-03-10
        • 2011-10-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多