在Perl中执行base36算法的最佳方法是什么?

时间:2010-04-19 21:03:24

标签: perl math base-n

在Perl中进行base36算法的最佳方法是什么?

更具体地说,我需要能够做到以下几点:

  • 在基数为36的正N位数字上运算(例如数字为0-9 A-Z)

    N是有限的,比如9

  • 提供基本算术,至少提供以下3:

    • 加法(A + B)

    • 减法(A-B)

    • 整个分区,例如地板(A / B)。

    • 严格地说,我真的不需要base10转换能力 - 数字将100%的时间都在base36中。所以我很好,如果解决方案没有实现从base36转换回base10,反之亦然。

我不太关心解决方案是否是强力“转换为基础10并返回”或转换为二进制,或者一些更优雅的方法“本机”执行baseN操作(如上所述,来自base10转换)不是要求)。我唯一的三个注意事项是:

  1. 符合上述最低规格

  2. 这是“标准”。目前我们正在使用基于base10转换的老本土模块,这种模块是手动完成的,这种模块很糟糕,很糟糕。

    我宁愿用一些常用的CPAN解决方案取而代之,而不是从头开始重新编写自己的自行车,但如果不存在更好的标准可能性,我完全有能力构建它。

  3. 它必须是快速的(虽然不是闪电般快)。需要1秒才能总结2个9位数的base36数字的东西比我自己可以推出的任何东西都要糟糕:)

  4. P.S。只是为了提供一些上下文,以防人们决定解决我的XY问题,除了回答上面的技术问题:)

    我们有一个相当大的树(作为一堆边存储在DB中),我们需要在该树的子集上叠加顺序。树的尺寸在深度和宽度方面都很大。树非常积极地更新(插入和删除以及分支移动)。

    这是通过具有3列的第二个表来完成的:parent_vertex, child_vertex, local_order,其中local_order是由A-Z0-9构建的9个字符的字符串(例如,基数36个数字)。

    其他注意事项:

    • 要求每个孩子的本地订单是唯一的(并且每个家长显然是唯一的),

    • 父母的任何完整的重新排序都有点昂贵,因此实现是尝试为具有X个孩子的父母分配 - 在0到36之间有些均匀分布的命令** 10- 1,因此几乎没有树插入导致完全重新排序。

3 个答案:

答案 0 :(得分:12)

Math::Base36怎么办?

答案 1 :(得分:9)

我假设Perl核心模块没问题?

如何使用本机(二进制)整数数学并使用POSIX::strtol()

从基数36结果转换

在转换到基础36的不同方法中存在 HUGE 的速度变化.Strtol比Math :: Base36快140倍:例如decode_base36和我在的转换子列表比Math :: Base36快2到4倍。它们还支持任何最大为62的整数。(通过在nums数组中添加字符可以轻松扩展。)

这是一个快速的基准:

#!/usr/bin/perl
use POSIX;
use Math::BaseCnv;
use Math::Base36 ':all';
use Benchmark;

{
    my @nums = (0..9,'a'..'z','A'..'Z');
    $chr=join('',@nums);
    my %nums = map { $nums[$_] => $_ } 0..$#nums;

    sub to_base
    {
        my ($base, $n) = @_;
        return $nums[0] if $n == 0;
        return $nums[0] if $base > $#nums;
        my $str = ''; 
        while( $n > 0 )
        {
            $str = $nums[$n % $base] . $str;
            $n = int( $n / $base );
        }
        return $str;
    }

    sub fr_base
    {
        my ($base,$str) = @_;
        my $n = 0;

        return 0 if $str=~/[^$chr]/;

        foreach ($str =~ /[$chr]/g)
        {
            $n *= $base;
            $n += $nums{$_};
        }
        return $n;
    }
}

$base=36;   
$term=fr_base($base,"zzz");

for(0..$term) { push @numlist, to_base($base,$_); }

timethese(-10, {
        'to_base' => sub { for(0..$#numlist){ to_base($base,$_); }  },
        'encode_base36' => sub { for(0..$#numlist){ encode_base36($_); }  },
        'cnv->to 36' => sub { for(0..$#numlist){ cnv($_); }  },
        'decode_base36' => sub { foreach(@numlist){ decode_base36($_); }  }, 
        'fr_base' => sub { foreach(@numlist){ fr_base($base,$_); }  },
        'cnv->to decimal' => sub { foreach(@numlist){ cnv($_,$base,10); }  },
        'POSIX' => sub { foreach(@numlist){ POSIX::strtol($_,$base);}},
} );

答案 2 :(得分:1)

我敢打赌转换到base10然后回来。

如果您不必经常这样做并且数字不是很大,那么这是最简单的(也就是最不复杂的=>最少数量的错误)方法。

当然,另一种方法是保存base10号码仅用于计算目的,但是,我不确定这是否可行或在你的情况下有任何优势