【问题标题】:Sorting hash keys by Alphanumeric sort通过字母数字排序对哈希键进行排序
【发布时间】:2014-04-13 13:52:37
【问题描述】:

我刚刚阅读了帖子Sorting alphanumeric hash keys in Perl?。但是我是从Perl开始的,理解的不是很清楚。

所以我有一个像这样的哈希:

  %hash = (
        "chr1" => 1,
        "chr2" => 3,
        "chr19" => 14,
        "chr22" => 1,
        "X" => 2,
    )

我正在尝试获得这样的输出:

chr1
chr2
chr19
chr22

但我得到这样的输出:

chr1
chr19
chr2
chr22

我已经编写了这段代码,但是它创建了上面的错误输出:

foreach my $chr (sort {$a cmp $b} keys(%hash)) {
    my $total= $hash{$chr};
    my $differentpercent= ($differenthash{$chr} / $total)*100;
    my $round=(int($differentpercent*1000))/1000;
    print "$chr\t$hash{$chr}\t$differenthash{$chr}\t$round\n";
}

打印出来:

chr1    342421    7449    2.175
chr10    227648    5327    2.34
chr11    220415    4468    2.027
chr12    213263    4578    2.146
chr13    172379    3518    2.04
chr14    143534    2883    2.008
chr15    126441    2588    2.046
chr16    138239    3596    2.601
chr17    122137    3232    2.646
chr18    130275    3252    2.496
chr19    99876    2836    2.839
chr2    366815    8123    2.214

我该如何解决这个问题?

【问题讨论】:

    标签: perl sorting hash alphanumeric


    【解决方案1】:

    更新请注意下面@Miller 对Sort::Naturally 模块的一些缺点的评论。

    您要求的是一种相对复杂的排序,它将每个字符串拆分为字母和数字字段,然后按词法对字母进行排序,并按值对数字进行排序。

    模块Sort::Naturally 会按照你的要求做,或者你可以写这样的东西。您似乎忽略了 X 键,因此我使用不区分大小写的排序将其排序到最后。

    use strict;
    use warnings;
    
    my %hash = map { $_ => 1 } qw(
        chr22  chr20  chr19  chr13  chr21  chr16  chr12  chr10  chr18
        chr17  chrY   chr5   chrX   chr8   chr14  chr6   chr3   chr9
        chr1   chrM   chr11  chr2   chr7   chr4   chr15
    );
    
    my @sorted_keys = sort {
        my @aa = $a =~ /^([A-Za-z]+)(\d*)/;
        my @bb = $b =~ /^([A-Za-z]+)(\d*)/;
        lc $aa[0] cmp lc $bb[0] or $aa[1] <=> $bb[1];
    } keys %hash;
    
    print "$_\n" for @sorted_keys;
    

    输出

    chr1
    chr2
    chr3
    chr4
    chr5
    chr6
    chr7
    chr8
    chr9
    chr10
    chr11
    chr12
    chr13
    chr14
    chr15
    chr16
    chr17
    chr18
    chr19
    chr20
    chr21
    chr22
    chrM
    chrX
    chrY
    

    使用Sort::Naturally 模块(您可能必须安装它)您可以改为编写此代码。

    use strict;
    use warnings;
    
    use Sort::Naturally;
    
    my %hash = map { $_ => 1 } qw(
        chr22  chr20  chr19  chr13  chr21  chr16  chr12  chr10  chr18
        chr17  chrY   chr5   chrX   chr8   chr14  chr6   chr3   chr9
        chr1   chrM   chr11  chr2   chr7   chr4   chr15
    );
    
    my @sorted_keys = nsort keys %hash;
    
    print "$_\n" for @sorted_keys;
    

    输出与上面相同。

    【讨论】:

    • @Borodin 您故意选择不使用Schwartzian transform 以提高速度效率。这可能是一个不错的选择,因为我希望我们只处理大约 300 个元素。但是,我觉得这很好奇,因为我几乎从未见过专家不在 perlmonks 上使用 ST。这是您有时会在实时代码中避免的过早优化吗?或者只是在这样的示例中避免了某些事情,因为它使用户能够更轻松地了解如何进行替代排序?
    • @Miller:当然。我总是使用标准排序,除非它被证明太慢了。即使我最终缓存了排序键(除了 ST 之外还有很多方法可以做到这一点),我通常会将原始排序代码留在旁边以记录它真正在做什么,因为它远非立即显而易见在 Schwartzian 变换中排序的内容。
    • @Borodin 我强烈建议将此模块推荐切换为Sort::Key::NaturalSort::Naturally 中存在错误,它忽略标点符号,只寻找字母和数字组之间的交替。例如对于这个数据qw(124:8 17:11);,它将在 17 之前排序 124,因为在内部它会去掉冒号,然后将它们按数字排序为单个数字。
    【解决方案2】:

    这也可以通过称为map-sort-map 的常见 Perl 习语来解决:

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    use Data::Dumper;
    
    my %hash = (
       "chr1"  => 1,
       "chr2"  => 3,
       "chr19" => 14,
       "chr22" => 1,
    );
    
    my @sorted = map  { $_->[0]             }
                 sort { $a->[1] <=> $b->[1] }
                 map  { [$_, (/chr(\d+)/) || 0]  } keys %hash;
    
    print Dumper \@sorted;
    __END__
    [
      'chr1',
      'chr2',
      'chr19',
      'chr22'
    ];
    

    注意:与@Borodin 不同,我选择了排序 X 到前面,因为它没有指定,所以我只选择了一个结尾。

    【讨论】:

    • 不,您的解决方案不是通过选择排在前面的:您根本没有满足它。将该元素添加到哈希中会使您的解决方案引发三个 Use of uninitialized value 警告。在这种情况下没有理由转换数据:它只会让代码更难理解。
    • @Borodin 在我在默认情况下编辑之前没有。
    • 如你所说,它仍然没有按字母数字排序。
    【解决方案3】:

    这是我很长时间以来一直这样做的方式......我正在从 Borodin 的帖子中窃取代码以供参考。如果您了解正则表达式,Borodin 的排序代码很容易理解。我更喜欢将复杂的排序放入 sub 中,否则它真的会变得混乱。不管怎样,你去吧:

    my %hash = (
        "chr1" => 1,
        "chr2" => 3,
        "chr19" => 14,
        "chr22" => 1,
        "X" => 2,
    );
    
    foreach my $key (sort {&sortalphanum} keys %hash)
    {
      print "  $key = $hash{$key}\n";
    }
    
    sub sortalphanum
    {
      my @aa = $a =~ /^([A-Za-z]+)(\d*)/;
      my @bb = $b =~ /^([A-Za-z]+)(\d*)/;
      lc $aa[0] cmp lc $bb[0] or $aa[1] <=> $bb[1];
    }
    

    【讨论】:

      【解决方案4】:

      你可以试试这个:

      #!/usr/bin/perl
      
      use warnings;
      use strict;
      
      my %records;
      while (<DATA>) {
          my ($key, undef) = split;
          $records{$key} = $_;
      }
      
      my @keys = sort {
          my ($aa) = $a =~ /(\d+)/;
          my ($bb) = $b =~ /(\d+)/;
          $aa <=> $bb;
      } keys %records;
      
      foreach my $key (@keys) {
          printf "$records{$key}";
      }
      
      
      __DATA__
      chr1    342421  7449    2.175
      chr10   227648  5327    2.34
      chr11   220415  4468    2.027
      chr12   213263  4578    2.146
      chr13   172379  3518    2.04
      chr14   143534  2883    2.008
      chr15   126441  2588    2.046
      chr16   138239  3596    2.601
      chr17   122137  3232    2.646
      chr18   130275  3252    2.496
      chr19   99876   2836    2.839
      chr2    366815  8123    2.214
      

      输出:

      $ perl t01.pl 
      chr1    342421  7449    2.175
      chr2    366815  8123    2.214
      chr10   227648  5327    2.34
      chr11   220415  4468    2.027
      chr12   213263  4578    2.146
      chr13   172379  3518    2.04
      chr14   143534  2883    2.008
      chr15   126441  2588    2.046
      chr16   138239  3596    2.601
      chr17   122137  3232    2.646
      chr18   130275  3252    2.496
      chr19   99876   2836    2.839
      

      【讨论】:

      • 它不工作,它打印:chrY chrX chrM chr7 chr20 chr22 chr14 chr8 chr19 chr1 chr6 chr11 chr17 chr21 chr16 chr18 chr3 chr12 chr15 chr4 chr2 chr9 chr >chr11 chr9 chr
      • @userbio:您在那里列出了您没有在问题中提出的字符串。描述您期望的完整范围值很重要,否则建议的解决方案将不起作用。
      猜你喜欢
      • 2015-02-07
      • 1970-01-01
      • 2016-04-27
      • 2014-08-03
      • 1970-01-01
      • 1970-01-01
      • 2013-06-06
      • 1970-01-01
      • 2011-12-26
      相关资源
      最近更新 更多