5

我正在为我正在学习 php 的项目编写一个 url 缩短函数,这是代码(顺便说一句,我想这global不是一件好事:P):

$alphabet = array(1 => "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
                "A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z",
                "0","1","2","3","4","5","6","7","8","9","_","-");

function shorten($id){
    global $alphabet;
    $shortenedId = "";
    while($id>0){
        $remainder = $id % 64;
        $id = $id / 64;     
        $shortenedId = $alphabet[$remainder].$shortenedId;
    }
    return $shortenedId;
}

代码取自这篇 Wikipedia 文章 并改编为 php。我的问题是,当我将 64 的倍数传递给函数时,我得到了错误的(出于我的目的)结果,例如 128 返回的 b 不正确,它应该是 aaa,但对于 3 位数字来说太长了数字。

另外我开始认为这段代码有问题,如果我在$id得到 nItOq 时通过 1'000'000'000'000... 我觉得这是错误的,因为像 bit.ly 这样的 url 缩短服务返回 6 number id 如果我使用它,我认为这个算法并不比他们的更好。

所以,两个问题:

  • 你在上面的代码中发现任何错误吗?
  • 如何管理 64-multiple ids?我是否必须忽略它们并传递给下一个?
4

8 回答 8

14

只需要进行一些小的调整,主要的两个是使字母零索引而不是一索引,并在除法之前从 id 中减去余数

function shorten($id)
{
    $alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-';
    $shortenedId = '';
    while($id>0) {
        $remainder = $id % 64;
        $id = ($id-$remainder) / 64;     
        $shortenedId = $alphabet{$remainder} . $shortenedId;
    };
    return $shortenedId;
}

这是一个进一步修改的版本......我只是喜欢

function shorten($id, $alphabet='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-')
{
    $base = strlen($alphabet);
    $short = '';
    while($id) {
        $id = ($id-($r=$id%$base))/$base;     
        $short = $alphabet{$r} . $short;
    };
    return $short;
}

编辑:排序连接与 OP 相同

于 2010-07-08T01:44:34.647 回答
5

如果您正在寻找相反的函数来获取 base64 数字并转换为 base10,这里有一些基于 JavaScript 的 PHP 答案:如何在 PHP 中将 base64 转换为 base10?

function lengthen($id) {
    $alphabet='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-';

    $number=0;
    foreach(str_split($id) as $letter) {
        $number=($number*64) + strpos($alphabet,$letter);
    }
    return $number;
}
于 2015-08-15T01:04:39.173 回答
2

这个怎么样:

function shorten_int($id){
    $hex = base_convert(id, 10, 16);
    $base64 = base64_encode(pack('H*', $hex));
    //$base64 = str_replace("/", "_", $base64); // remove unsafe url chars
    //$base64 = str_replace("+", "-", $base64);
    //$base64 = rtrim($base64, '='); // Remove the padding "=="
    $replacePairs = array('/' => '_',
                          '+' => '-',
                          '=' => '');
    $base64 = strtr($base64, $replacePairs); // optimisation
    return $base64;
}
于 2015-08-07T11:21:52.600 回答
1

顺便说一下,查看 base_convert() 函数(http://php.net/manual/en/function.base-convert.php):

echo base_convert(1000000000, 10, 36);

不过,36 是它可以转换到的最长基数。但是在评论部分我发现了这个:

function dec2any( $num, $base, $index=false ) {
    if (! $base ) {
        $base = strlen( $index );
    } else if (! $index ) {
        $index = substr( "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" ,0 ,$base );
    }
    $out = "";
    for ( $t = floor( log10( $num ) / log10( $base ) ); $t >= 0; $t-- ) {
        $a = floor( $num / pow( $base, $t ) );
        $out = $out . substr( $index, $a, 1 );
        $num = $num - ( $a * pow( $base, $t ) );
    }
    return $out;
}

echo dec2any(1000000000, 64, "_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");

也许会有所帮助?

于 2010-07-08T10:17:26.800 回答
1

Paul Greg 创建了一些 PHP 代码,可将 Base-10 转换为另一个 base。这可以进行测试并在此处下载代码:

http://www.pgregg.com/projects/php/base_conversion/base_conversion.php

我正在使用这种方法将数据库行 ID 转换为 Base-64。一旦这些数字被缩短,它们就可以在 URL 中使用。 [细节]

于 2012-03-30T21:47:04.317 回答
1

这两个功能非常方便,感谢@malhal:

function shorten_int($id)
{
    $id=dechex($id);
    $id=strlen($id)%2===0?hex2bin($id):hex2bin('0'.$id);
    $id=base64_encode($id);
    $id=strtr($id, array('/'=>'_', '+'=>'-', '='=>''));
    return $id;
}

function unshorten_int($id)
{
    $id=strtr($id, array('-'=>'+', '_'=>'/'));
    $id=base64_decode($id);
    $id=bin2hex($id);
    return base_convert($id, 16, 10);
}

echo shorten_int(43121111)."\n";
echo unshorten_int(shorten_int(43121111))."\n";
于 2018-10-31T11:38:26.183 回答
0

这是 Nathans 代码的变体,用于处理大于 PHP_INT_MAX 的大整数。

这使用应该内置在 Windows 服务器上的BC 数学函数,但这需要作为 Unix 服务器上的可选扩展启用。此解决方案还需要几个自定义 BC 函数来处理我从Alix Axel 的帖子中复制的地板和圆形函数。

function shorten($value, $alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-') {
    $base = strlen($alphabet);
    $result = '';
    while ($value) {
        $mod = bcmod($value, $base);
        $value = bcfloor(bcdiv($value, $base));
        $result = $alphabet[$mod] . $result;
    }
    return $result;
  }

function lengthen($value, $alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-') {
    $base= strlen($alphabet);
    $result = '';
    for($i = 0, $limit = strlen($value); $i < $limit; $i++) {
        $result = bcadd(bcmul($base, $result), strpos($alphabet, $value[$i]));
    }
    return $result;
}

function bcceil($number) {
    if (strpos($number, '.') !== false) {
        if (preg_match("~\.[0]+$~", $number)) return bcround($number, 0);
        if ($number[0] != '-') return bcadd($number, 1, 0);
        return bcsub($number, 0, 0);
    }
    return $number;
}

function bcfloor($number) {
    if (strpos($number, '.') !== false) {
        if (preg_match("~\.[0]+$~", $number)) return bcround($number, 0);
        if ($number[0] != '-') return bcadd($number, 0, 0);
        return bcsub($number, 1, 0);
    }
    return $number;
}

function bcround($number, $precision = 0) {
    if (strpos($number, '.') !== false) {
        if ($number[0] != '-') return bcadd($number, '0.' . str_repeat('0', $precision) . '5', $precision);
        return bcsub($number, '0.' . str_repeat('0', $precision) . '5', $precision);
    }
    return $number;
}

在 Windows(32 位)上运行 PHP 5.6 的示例

foreach ([0, 1, 9, 10, 115617, bcsub(PHP_INT_MAX, 1), PHP_INT_MAX, bcadd(PHP_INT_MAX, 1234567890)] as $value) {
    $short = shorten($value);
    $reversed = lengthen($short);
    print shorten($value) . " ($value)<br>";
    if ("$value" !== $reversed) {
        print 'ERROR REVERSING VALUE<br>';
    }
}

输出

0 (0)
1 (1)
9 (9)
a (10)
sex (115617)
1----_ (2147483646)
1----- (2147483647)
39Bwbh (3382051537)

如果 ID 是公开的,请避免在字符串中使用元音(例如,115617 缩写为 sex)。这将是应该提供安全词的基本 54 版本。

$alphabet = '0123456789bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ_-';
于 2020-08-15T03:54:37.420 回答
-1

您可以使用pack.

$int = 1129717211140920362;

$byte = pack('J*', $int);    
echo base64_encode($byte); //= D62P0WqzFCo=

它将导致D62P0WqzFCo=,这是正确的,因为$int是 int64 并使用 64 位。Base64 为每个字符使用 6 位,因此它们需要大约 11 个字符。

解码使用:

$base64 = 'D62P0WqzFCo=';

$byte = base64_decode($base64);
echo unpack('J*',  $byte)[1]; //= 1129717211140920362

它会返回1129717211140920362。;)


它基于 葡萄牙语 Stackoverflow的答案。

于 2018-01-01T04:43:16.337 回答