アシアルの中の人が技術と思いのたけをつづるブログ

base_convertで文字列圧縮

タグ [  Tech  ]
こんにちは、アシアルの岡本です。

とある事情により16進数の文字列を圧縮する必要に迫られました。
URLに載せる必要があるのでバイナリは駄目ですzlib関数は使えません。

「PHPのbase_convert関数を使って36進数に変えれば楽勝!」
と思ったのが運のつき。
長く険しい文字列圧縮の旅(大袈裟!)が始まってしまいました。

base_convert関数は数値の基数を任意に変換するPHPの標準関数です。
こいつを使えば10進数の数字を16進数に変換したり、
16進数の数字を2進数に変換するといったことが一発で行えます。

この関数を活用し「16777216」という10進数を16進数に圧縮した場合、
「ffffff」と8文字から6文字に25%も圧縮できるのです。

しかし、PHPマニュアルの警告には
「大きな数値で base_convert() を使用すると、
精度が失われてしまうことがあります。」
とありました。
試してみましたが、残念ならが事実でした。
80バイトもあるような長い文字列で利用すると、似てるけど少し違う数字になってしまいます。

残念です。

「さて、どうしよう・・・」

自力で進数の変換コードを書く気力のなかった、
文系プログラマの僕はbase_convertに拘って、
文字列を適当な長さ(13桁までいけました)で区切る選択肢を選びました。

そうして作り上げた圧縮クラスが以下のソースコードになります。

  1. <?php
  2. /**
  3.  * compress_by_base_convert 
  4.  * N進数の文字列をもっと大きなM進数に置き換えて文字を圧縮するクラス
  5.  * 
  6.  * @copyright Asial
  7.  * @author Yuki Okamoto <yuki@asial.co.jp> 
  8.  */
  9. class compressByBaseConvert
  10. {
  11.   var $compress_base = 36; 
  12.   var $uncompress_base = 16; 
  13.   var $split = 13; 
  14.   var $delimiter = "-";
  15.  
  16.   /** 
  17.    * compress 
  18.    * 圧縮します
  19.    * 
  20.    * @param  string $string 
  21.    * @return string
  22.    */
  23.   public function compress ($string)
  24.   {
  25.     // 分割しやすいよう0でパディングして、桁を分割したい数の倍数にする。
  26.     $length = strlen($string);
  27.     $padding_length = $this->split - ($length % $this->split);
  28.     $join_length = ($length + $padding_length);
  29.     $string = str_pad($string, $join_length, "0", STR_PAD_LEFT);
  30.  
  31.     // 分割し、0パディングを解除する
  32.     $string_array = str_split($string, $this->split);
  33.     $string_array[0] = ltrim($string_array[0], "0");
  34.  
  35.     // base_convertで圧縮
  36.     foreach ($string_array as $split) {
  37.       $compress = base_convert($split, $this->uncompress_base, $this->compress_base);
  38.       $compress_array[] = $compress;
  39.     }   
  40.  
  41.     // 連結して返す
  42.     return implode($this->delimiter, $compress_array);
  43.   }
  44.   /** 
  45.    * uncompress 
  46.    * 解凍します
  47.    * 
  48.    * @param  string $string
  49.    * @return string
  50.    */
  51.   public function uncompress($string)
  52.   {
  53.     // 配列に戻す
  54.     $string_array = explode($this->delimiter, $string);
  55.  
  56.     // base_convertで解凍
  57.     foreach ($string_array as $split) {
  58.       $uncompress = base_convert($split, $this->compress_base, $this->uncompress_base);
  59.       $uncompress_array[] = $uncompress;
  60.     }   
  61.  
  62.     // 途中の0が落ちないように0パディングして連結
  63.     $uncompress_join = ""; 
  64.     foreach ($uncompress_array as $uncompress) {
  65.        $uncompress_join .= str_pad($uncompress, $this->split, "0", STR_PAD_LEFT);
  66.     }   
  67.  
  68.     // 上の桁の0は不要なのでtrimして削除
  69.     $uncompress_join = ltrim($uncompress_join, 0); 
  70.  
  71.     return $uncompress_join;
  72.   }
  73. }
  74. // 実証コード
  75. $compress = new compressByBaseConvert();
  76. $source   = "153733d3cd194c6b60e50f1dcf5cdf703a3a363031302d30352d31382036303a33313a3aa9e9a25ea53fb8a2";
  77. $archive = $compress->compress($source);
  78. $return = $compress->uncompress($archive);
  79. var_dump($source, $archive, $return);


分割は、思ったより簡単ではありませんでした。
分割した左辺に0があった場合は圧縮→解凍したときに0が取れてしまうので、
パディングして直す必要があったり色々大変でした。

このクラスは、完成というには程遠いですが
(元の進数と圧縮に使う進数をsetするメソッドすらない…)
ニーズがあるか知りたいので公開してしまいます。
自由に使ってもらって構いませんが、無保証です。
うん、後で弊社の小川に説教されるかも…

手が空いたら、今度は自分でconvert処理を書くかもしれません。
base_convertでは36進数が限界で62進数が使えないので。

「クラス」といえば
アシアルのスクールにオブジェクト編が追加されました。
PHPオブジェクト指向編 効率的にプログラミングする手法

講師の小川の方が丁寧に正しいオブジェクトの書き方を教えますので、
PHPでオブジェクト使ってみたい方は是非一度ご検討ください。

コメント

    • $hex = 'f0efd377976f';
      base64_encode(pack('H*', $hex));
      じゃだめなんですか?
    • MugeSoさん
      コメントありがとうございます!

      検証コードを書いてみましたが、うまく行きました。単純にbase64を掛けるのでなく
      packして文字数を減らした上でbase64を掛ければ圧縮できるんですね。

      今回は色々と勉強になりました。

      ●コード
      <?php
      $hex = '153733d3cd194c6b60e50f1dcf5cdf703a3a363031302d30352d31382036303a33313a3aa9e9a25ea53fb8a2';
      $pack = pack('H*', $hex);
      $base64 = base64_encode($pack);

      var_dump($hex);
      var_dump($pack);
      var_dump($base64);
      var_dump(unpack('H*', base64_decode($base64)));

      ●出力結果
      string(88) "153733d3cd194c6b60e50f1dcf5cdf703a3a363031302d30352d31382036303a33313a3aa9e9a25ea53fb8a2"
      string(44) "73Lk`::6010-05-18 60:31::"
      string(60) "FTcz080ZTGtg5Q8dz1zfcDo6NjAxMC0wNS0xOCA2MDozMTo6qemiXqU/uKI="
      array(1) {
      [1]=>
      string(88) "153733d3cd194c6b60e50f1dcf5cdf703a3a363031302d30352d31382036303a33313a3aa9e9a25ea53fb8a2"
      }

コメントフォーム

認証
captcha_key
 

トラックバック