对键包含非字母数字字符的哈希进行排序

时间:2016-01-21 11:16:49

标签: perl sorting hash natural-sort

我有如下的哈希:

my %hash=( '(293 to 296)'   => 2,
           '(3118 to 3121)' => 2,
           '(330 to 333)'   => 2,
           '(2126 to 2129)' => 2,
           '(1999 to 2002)' => 2,
           '(2138 to 2141)' => 9,
           '(771 to 774)'   => 4,
           '(2016 to 2019)' => 1,
           '(888 to 891)'   => 5,
           '(3102 to 3105)' => 1,
        );

我想使用键对哈希进行排序,其中键包含括号。我试过以下代码,

foreach $key(sort {$b <=> $a} keys %hash)
{
    print $key;
}

我得到了以下内容,但未按数字排序:

  

(888至891)(2016至2019)(293至296)(3118至3121)(3102至3105)(330   至333)(1999至2002)(2126至2129)(2138至2141)(771至774)

我期待输出,数字排序如下。请建议我实现以下目标:

(293 to 296)
(330 to 333)
(771 to 774)
(888 to 891)
(1999 to 2002)
(2016 to 2019)
(2126 to 2129)
(2138 to 2141)
(3102 to 3105)
(3118 to 3121)                             

5 个答案:

答案 0 :(得分:4)

sort的作用是将$a$b传递到一个函数中,然后返回-10+1

最简单的 - 对第一个数字进行排序 - 将如下所示:

sort { $a =~ s/.(\d+).*/$1/r <=> $b =~ s/.*(\d+).*/$1/r } keys %hash

这将从每个键中提取第一个数值,比较并返回该比较值。

当然,如果您的范围重叠,这将无法按照您的方式运作 - 如果您有以下情况,您将不得不变得更加复杂:

100到200    150至180    120至205

他们应该如何分类?无论哪种方式 - 你编写一个'{1}}和$a'有效'的子程序并执行比较。这里一个有用的技巧是'标准'排序运算符 - $b<=> - 返回零,因此可以使用cmp进行缩写。

所以:

||

如果第一次比较为零,则评估第二次比较。

或者您可以计算中间值:

sub compare_numbers {
   my @a = $a =~ m/(\d+)/g;
   my @b = $b =~ m/(\d+)/g; 
   return ( $a[0] <=> $b[0] 
         || $a[1] <=> $b[1] )
}

您将以与上述类似的方式使用其中任何一种:

sub compare_numbers {
   my @a = $a =~ m/(\d+)/g;
   my @b = $b =~ m/(\d+)/g; 
   return ( ($a[1] - $a[0] / 2 + $a[0]) <=> ($b[1] - $b[0] / 2 + $b[0])
}

答案 1 :(得分:2)

'(293 to 296)'不是一个数字(并且不会以数字开头),因此尝试按数字排序并没有任何意义。

您可以从中提取第一个数字并对其进行排序。

($a) = ($a =~ /(\d+)/);
($b) = ($b =~ /(\d+)/);

答案 2 :(得分:2)

问题是像(293 to 296)这样的字符串没有数值。如果你有use warnings 'all'就位,你会看到多个警告,如

  

参数“(293到296)”在排序

中不是数字

并且每个键的计算结果为零,因此就sort而言它们都是相等的

因此,您必须从每个值中提取一个数字,以便在数字排序中使用。我只想抓住每个范围的下限并按此排序。

use strict;
use warnings 'all';
use feature 'say';

my %hash = (
    '(293 to 296)'   => 2,
    '(3118 to 3121)' => 2,
    '(330 to 333)'   => 2,
    '(2126 to 2129)' => 2,
    '(1999 to 2002)' => 2,
    '(2138 to 2141)' => 9,
    '(771 to 774)'   => 4,
    '(2016 to 2019)' => 1,
    '(888 to 891)'   => 5,
    '(3102 to 3105)' => 1,
);

my @keys = sort {
  my ($aa, $bb) = map /(\d+)/, $a, $b;
  $aa <=> $bb;
} keys %hash;

say for @keys;

输出

(293 to 296)
(330 to 333)
(771 to 774)
(888 to 891)
(1999 to 2002)
(2016 to 2019)
(2126 to 2129)
(2138 to 2141)
(3102 to 3105)
(3118 to 3121)

使用List::MoreUtilsList::UtilsBy中的nsort_by功能可以更加简洁明了

use List::MoreUtils 'nsort_by';

say for nsort_by { /(\d+)/ and $1 } keys %hash;

此代码的输出与上述

的输出相同

答案 3 :(得分:1)

试试这个

在下面的脚本中,我使用模式匹配来删除带有( )标记的/r。 它有助于保留替换中的原始数据。然后它将按数字排序。

my %hash=( '(293 to 296)'   => 2,
           '(3118 to 3121)' => 2,
           '(330 to 333)'   => 2,
           '(2126 to 2129)' => 2,
           '(1999 to 2002)' => 2,
           '(2138 to 2141)' => 9,
           '(771 to 774)'   => 4,
           '(2016 to 2019)' => 1,
           '(888 to 891)'   => 5,
           '(3102 to 3105)' => 1,
        );



foreach my $i (sort { $a=~s/\(//rg <=> $b=~s/\(//rg }  keys %hash)
{
    print "$i\n";

}

答案 4 :(得分:0)

您可以使用其中一个“自然”对值进行排序的CPAN模块(例如您可以使用Sort::Naturally)。

这会隐藏正在发生的事情。因此,出于教育目的,我喜欢@Sobrique@Borodin@Quentin的解释。

use Sort::Naturally;
my @nsorted ;
@nsorted = nsort ( <DATA> ) ;
print @nsorted;

__DATA__
(293 to 296)
(3118 to 3121)
(330 to 333)
(2126 to 2129)
(1999 to 2002)
(2138 to 2141)
(771 to 774)
(2016 to 2019)
(888 to 891)
(3102 to 3105)

<强>输出:

(293 to 296)
(330 to 333)
(771 to 774)
(888 to 891)
(1999 to 2002)
(2016 to 2019)
(2126 to 2129)
(2138 to 2141)
(3102 to 3105)
(3118 to 3121)