按照两个参数对哈希进行排序

3

我有一个哈希表,其键的格式如下:

scaffold_902_159
scaffold_2_1980420
scaffold_2_10
scaffold_10_402

我希望以以下格式打印哈希排序后的内容:
scaffold_2_10
scaffold_2_1980420
scaffold_10_402
scaffold_902_159

首先,我需要按照第一个数字的顺序进行排序,然后再按照最后一个数字进行排序。我不想使用正则表达式搜索“scaffold_”,因为这可能会有所变化。我的意思是,我可以使用其他格式的哈希值,例如“blablabla_NUMBER_NUMBER”或“blablablaNUMBER_NUMBER”。键的最后一部分_NUMBER是唯一固定的部分。
我有以下代码,但只能按照第一个数字的顺序进行排序:
my @keys = sort {
          my ($aa) = $a =~ /(\d+)/;
          my ($bb) = $b =~ /(\d+)/;
          $aa <=> $bb;
        } keys %hash;
foreach my $key (@keys) {
   print $key;
}

有什么建议吗?

1
相关问题请参考:https://dev59.com/zF7Va4cB1Zd3GeqPJm2D - 要在你的字符串中找到这些数字,你需要做一些工作,但那里被采纳的答案是你所需要的构造方式。你可能需要结合 Schwartzian Transform 使用。 - simbabque
是否存在固定出现次数的blablabla_NUMBER,或者blablabla&NUMBER可能会像blablabla_blablabla_NUMBER一样发生变化? - AbhiNickz
@AbhiNickz,再思考一下你的问题,也许有时中间的数字不是数字。这些情况应该出现在最后,并且当然要按第二个数字(始终存在)排序。 - cucurbit
我们在我的回答下进行了一些讨论,关于你最后的评论。你能否为我们澄清一下?谢谢 :) - simbabque
2个回答

6

救星来了——Sort::Naturally

#!/usr/bin/perl
use strict;
use warnings;
use Sort::Naturally qw(nsort);
my %hash = (
                scaffold_902_159 => 'v1',
                scaffold_2_1980420 => 'v2',
                scaffold_2_10 => 'v3',
                scaffold_10_402 => 'v4',
            );
print "$_\n" for nsort keys %hash;

输出:

scaffold_2_10
scaffold_2_1980420
scaffold_10_402
scaffold_902_159

根据您的查询,尝试了一些没有数字中间的键。
#!/usr/bin/perl
use strict;
use warnings;
use Sort::Naturally qw(nsort);
my @keys = qw(
    should_come_last_9999_0
    blablabla_10_403
    scaffold_902_159
    scaffold_2_1980420
    scaffold_2_10
    scaffold_10_402
    blablabla902_1
    blablabla901_3
);
print "$_\n" for nsort @keys;

输出:

blablabla_10_403
blablabla901_3
blablabla902_1
scaffold_2_10
scaffold_2_1980420
scaffold_10_402
scaffold_902_159
should_come_last_9999_0

谢谢!我会尝试并接受答案,如果它有效的话。你知道如果中间没有数字会发生什么吗? - cucurbit
会起作用的。就像模块的描述所说的那样,“Sort::Naturally -- 字典排序,但数字部分按数字排序”。 - Chankey Pathak

3

这个排序涉及到两列,并使用Schwartzian变换从字符串中创建这些列。

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

my @keys = qw(
    scaffold_902_159
    scaffold_2_1980420
    scaffold_2_10
    scaffold_10_402
);

@keys =
    map { $_->[0] }                                               # transform back
    sort { $a->[1] <=> $b->[1] || $a->[2] <=> $b->[2] }           # sort
    map {                                                         # transform
        m/(\d+)(?:\D+(\d+))/;
        [ $_, ( defined $2 ? ( $1, $2 ) : ( 0xffffffff, $1 ) ) ]
    } @keys;

say for @keys;

输出:

scaffold_2_10
scaffold_2_1980420
scaffold_10_402
scaffold_902_159

初始转换map返回的数据结构如下:

[ 'scaffold_902_159', 902, 159 ]
sort 首先使用索引1(即902)进行数值排序<=>。如果 RHS 和 LHS 相等,则该运算符返回 0,因此或者 || 继续执行右表达式,然后按索引2(即159)进行排序。

由于你说第一个数字是可选的,如果只有第二个数字,则这些元素应该排在最后,因此我们必须用一个非常大的数字进行替换。不涉及 64 位整数,0xffffffff 是我们可以制造的最高数字。

第二个 map 从数组引用的索引 0 中提取完整的键。

如果我们向输入添加一些其他内容,例如你建议的 blablablaNUMBER_NUMBER,它仍然只会在数字上进行排序,并完全忽略字符串部分。

my @keys = qw(
    should_come_last_9999_0
    blablabla_10_403
    scaffold_902_159
    scaffold_2_1980420
    scaffold_2_10
    scaffold_10_402
    no_first_number_1
);

这是输出结果:
scaffold_2_10
scaffold_2_1980420
scaffold_10_402
blablabla_10_403
blablabla902_1
scaffold_902_159
should_come_last_9999_0
no_first_number_1

1
这条评论 https://dev59.com/vJzha4cB1Zd3GeqPEGBy#kiEaoYgBc1ULPQZFmoIb 使我的回答无效。 - simbabque
我注意到Sort::Naturally在处理这种数据集时表现得非常好。请参见我的答案第二部分的输出。 - Chankey Pathak
@ChankeyPathak 是这样的。但是我理解这个问题是它不应该按单词排序,而你的方法却是这样做的。我认为这在问题中有点含糊不清。你的解决方案中 no_first_number_1 会发生什么? - simbabque
blablabla_10_403,no_first_number_1,scaffold_2_10,scaffold_2_1980420,scaffold_10_402,scaffold_902_159,should_come_last_9999_0。我认为这是正确的,因为它按字母顺序对第一列进行了排序。 - Chankey Pathak
哦,我明白了。哈!让我们等一下。 - Chankey Pathak
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接