【问题标题】:Base10 to base64 url shorteningBase10 到 base64 网址缩短
【发布时间】:2010-07-07 23:59:46
【问题描述】:

我正在为我正在学习 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;
}

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

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

那么,两个问题:

  • 您发现上述代码中有任何错误吗?
  • 如何管理 64 多个 id?我是否必须忽略它们并传递给下一个?

【问题讨论】:

  • 你不需要在那里使用 global (我看不出有什么理由吗?)。事实上,多年来不建议在 PHP 中使用全局变量(至少从 PHP5 的第一个版本开始)。改用依赖注入。
  • @Richard Knop:没有那个变量$alphabet 是无法访问的。
  • 哦,好吧,我现在明白了,范围已经改变,因为你在一个函数中。但是为什么不直接将 $alphabet 作为第二个参数传递给函数呢?真的,不建议在 PHP 中使用全局变量。
  • 嗯,好吧,我想我会像 nathan 一样把它放在函数中。
  • 在提出这个问题时,我们没有code review 站点。如果有人想要审查他们的代码,我们现在就这样做

标签: php url-shortener


【解决方案1】:

只需要几个小调整,主要的两个是使字母表从零索引而不是一索引,并在除法之前从 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 相同

【讨论】:

  • 这个$shortenedId .= $alphabet{$remainder};$shortenedId = $alphabet[$remainder].$shortenedId; 一样吗?在第二个我确定新的数字是附加在之前,你写的代码也是这样吗?
  • @Montecristo $shortenedId .= $alphabet{$remainder};与 $shortenedId = $shortenedId 相同。 $alphabet{$remainder};
  • @Richard Knop, @Montecristo :感谢您的发现,我已经编辑了要按照原始内容连接的函数
  • 可以解短吗?
  • 是的,可以解短,答案在这里stackoverflow.com/a/32020520/728236
【解决方案2】:

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

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

    $number=0;
    foreach(str_split($id) as $letter) {
        $number=($number*64) + strpos($alphabet,$letter);
    }
    return $number;
}

【讨论】:

    【解决方案3】:

    这个怎么样:

    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;
    }
    

    【讨论】:

    【解决方案4】:

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

    echo base_convert(1000000000, 10, 36);
    

    36 是它可以转换成的最长基数。但是在 cmets 部分我发现了这个:

    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");
    

    也许会有帮助?

    【讨论】:

    • 感谢您发布此内容,是的,我确实找到了它,尽管我无法理解它......所以我使用了另一种方法,我不喜欢在我的项目中使用我不理解的代码。 :D
    【解决方案5】:

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

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

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

    【讨论】:

      【解决方案6】:

      这两个功能很方便,感谢@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";
      

      【讨论】:

        【解决方案7】:

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

        这使用了应该内置在 Windows 服务器上的 BC Maths Functions,但这需要作为 Unix 服务器上的可选扩展启用。此解决方案还需要几个自定义 BC 函数来处理我从 post by 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)。这将是应该提供安全字词的 base 54 版本。

        $alphabet = '0123456789bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ_-';
        

        【讨论】:

          【解决方案8】:

          您可以使用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 in Portuguese的答案。

          【讨论】:

          • @RafaelLima,为什么错了? “字符串”是一个pack,它将整数(base10)转换为字符串形式。 base64 没有直接编码为字符串,它实际上编码为 int64。假设你有一个1 作为字符串,你以MQ== 结束而不是AAAAAAAAAAE=,这是大结束顺序的 int64,所以它需要一个数字并在 int64 中正确转换。
          猜你喜欢
          • 1970-01-01
          • 2010-11-25
          • 1970-01-01
          • 1970-01-01
          • 2018-12-04
          • 1970-01-01
          • 2013-02-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多