【问题标题】:Perl: fast sorting hash keys with positive and negative non-integer valuesPerl:快速排序具有正负非整数值的哈希键
【发布时间】:2016-03-01 21:40:20
【问题描述】:

我有一个基因和分数列表,我想根据分数对基因进行排序。列表很大(约 4200 万行),Perl sort 函数太慢了。

使用 Sort::External 模块我可以加快这个过程,但它只正确排序正整数。这是我尝试过的(我只展示了 10 个案例):

use warnings;
use strict;
use Sort::External;

my %hash = (
    'gene1' => -19.86,
    'gene2' => -12.90,
    'gene3' => 4.07,
    'gene4' => 22.59,
    'gene5' => 55.43,
    'gene6' => 55.42,
    'gene7' => 9.89,
    'gene8' => 27.51,
    'gene9' => 6.43,
    'gene10' => 59.65
);

my @sortkeys;
while ( my ( $gene, $score ) = each %hash ) {
    push @sortkeys, ( pack( 'n', $score ) . $gene );
}
my $sortex = Sort::External->new( mem_threshold => 1024**2 * 8 );
$sortex->feed(@sortkeys);
$sortex->finish;
my @sorted;
while ( defined( $_ = $sortex->fetch ) ) {
    push @sorted, substr( $_, 2 );
}

foreach (@sorted){
    print join("\t",$_,$hash{$_}),"\n";
}

这是输出:

gene3   4.07
gene9   6.43
gene7   9.89
gene4   22.59
gene8   27.51
gene5   55.43
gene6   55.42
gene10  59.65
gene1   -19.86
gene2   -12.9

对具有正负非整数值的哈希键进行快速排序的最佳解决方案是什么?

【问题讨论】:

  • 你为什么要打包未签名的?
  • 数据库和表索引相当擅长。
  • 你能给出一个“巨大”有多大的大致概念吗?我们说的是一百万行还是十亿行?
  • @stark,没关系。 pack('n', -100)pack('S>', -100)pack('s>', -100) 都产生相同的结果。 n 是这些选项中最熟悉的。
  • @Sobrique ~4200 万行。

标签: perl sorting hash negative-number


【解决方案1】:

如果你有整数,你可以使用以下方法处理负整数:

# Supports [32767, -32768]
pack( 'n', $score + 0x8000 )

但这认为 55.42 和 55.43 相等。

您可以对浮点数使用相同的方法。

  1. 将数字标准化为正数。
  2. 填充左侧的数字,使所有数字的小数点对齐。

最灵活:

# Supports ]67231, -32768], two decimal places.
sprintf( '%8.2f', $score + 0x8000 )

但您也可以转换为整数以生成更短的字符串:

# Supports [327, -327], two decimal places.
pack( 'n', sprintf( '%.0f', $score * 100 ) + 0x8000 )

# Supports [21_474_836, -21_474_836], two decimal places.
pack( 'N', sprintf( '%.0f', $score * 100 ) + 0x8000_0000 )

sprintf '%.0f' 用于四舍五入到最接近的整数,尽管它可能不会在所有机器上都这样做。)

【讨论】:

  • 假设分数为 -327.68..327.67,在打包前乘以 100。如果需要更大的范围和/或更高的精度,请切换到 pack 'N'。尝试使用填充使它们成为字符串将花费更长的时间并产生更大的数据进行排序。
  • FWIW 在实践中,sprintf 在我知道的每个平台上四舍五入到最近(通常使用银行家的四舍五入),但实际上 perl 使用 C 库,甚至 posix 只是说“低阶数字应以实现定义的方式四舍五入。”
【解决方案2】:

您可以运行两种排序,一种用于负数,一种用于非负数。

【讨论】:

    【解决方案3】:

    如果相对较小的整数足够精确来表示浮点数,那么简单的桶排序就可以了。选择$scale$offset 使您的最低/最高分数落入正整数范围:

    my @buckets;
    while(my ($gene, $score) = each %hash) {
        push @{$buckets[$score * $scale + $offset], $gene;
    }
    

    在我的系统上,一个包含 2**20 个空 arrayrefs 的数组占用 45 MB,在这个数据集大小下应该不会有太多开销。

    【讨论】:

      猜你喜欢
      • 2010-10-20
      • 2013-09-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-06
      • 1970-01-01
      • 2012-01-08
      相关资源
      最近更新 更多