【PHP】"mb_range()"（マルチバイト対応版 "range"）〜 MD5 ハッシュ値を 10 桁に圧縮する 〜

> PHP の[`range`](http://php.net/manual/ja/function.range.php) 関数の範囲を平仮名・カタカナ・漢字にまで対応させたい。

つまり `range` のマルチバイト版 `mb_range` 関数が欲しい。こんなの↓

```php
$a = mb_range( 'あ', 'お' );
print_r( $a );
```
```php:出力結果
Array
(
    [0] => あ
    [1] => ぃ
    [2] => い
    [3] => ぅ
    [4] => う
    [5] => ぇ
    [6] => え
    [7] => ぉ
    [8] => お
)
```

- 「こんなの何に使うの？」という方は TL; DR 参照

## TS; DR（マルチバイト対応版`range()`）

UTF-8 の文字コード表の順になりますが、**平仮名だけでなく漢字も範囲対象にして一気に得ることができます**。

```php:mb_range()
<?php
/**
 * range('A', 'Z') のマルチバイト対応版
 *
 * @author Rodney Rehm (https://gist.github.com/rodneyrehm/1306118)
 * @url    Latest code https://qiita.com/KEINOS/items/f0aa3cf476455390d95f
 *
 * @param  string $start 開始文字
 * @param  string $end   終了文字
 * @return array         開始から終了までの文字の配列
 */
function mb_range($start, $end)
{
    // $start と $end が同じ場合は何もしない
    if ($start == $end) {
        return array($start);
    }

    $_result = array();

    // $start と $end のユニコードを取得
    list(, $_start, $_end) = unpack("N*", mb_convert_encoding($start . $end, "UTF-32BE", "UTF-8"));

    // 処理方向を検知する
    $_offset  = $_start < $_end ? 1 : -1;
    $_current = $_start;

    // BOM なし UTF-32
    while ($_current != $_end) {
        $_result[] = mb_convert_encoding(pack("N*", $_current), "UTF-8", "UTF-32BE");
        $_current += $_offset;
    }
    $_result[] = $end;

    return $_result;
}

```

- [オンラインで動作を見る](https://paiza.io/projects/_wsElm-fHrzkA-_5fwLqcQ) @ paiza.IO


## TS; DR（`mb_range` の使いみち）

### `range` 関数、`mb_range` ユーザ関数とは

PHP の `range` 関数は`range(1,9);` を `[1,2,...,9]`と、指定した範囲の整数の配列を作成してくれます。また、数値だけでなく `range('A','Z');` -> `['A','B',...,'Z']` のように **ASCII コード表の範囲も取得できます**。

`mb_range` ユーザー関数は、この `range()` を**マルチバイト対応**させて「`mb_range( 'あ', 'ん' )`」などとして UTF-8 の文字コード表内の指定範囲を配列で得たい人のためのユーザー関数です。

### 何に使うの？

何に使うのか。ご覧の通り「あ,い,う,え,お」と並ばないため**余り用途はない**かもしれません。「あ,い,う,え,お」と並ばせたい場合は `preg_match` などを利用した方が賢いかもしれません。

`mb_range()` の使いみちの１つとしては、**文字列の長さの圧縮**に使えます。つまり文字列を短くできるということです。（データの圧縮ではありませんので注意）

#### アルファベットが足りないなら漢字を使えばいいじゃない

`0-9` は 10 文字で 10 進数を表現します。16 進数の場合は `0-9` `a-f` の 16 文字です。同様に `0-9` `a-z` `A-Z` `+` `/` の 64 文字（62文字＋記号2文字）を使えば 64 進数になります。「進数」ということは**１桁で、その文字数ぶんを表現できる**ということです。逆に言えば ASCII の場合は可視化できる範囲は `!` 〜 `~` までの 92 文字です。

しかし、時代は UTF-8 標準。そう、**漢字まで使っちゃえば、数値の桁数をより短くできる**ということです。

数値といえば、「長ければ長いほど良いが、できれば短くしたい」の代表が[ハッシュ関数によるハッシュ値の長さ](https://qiita.com/KEINOS/items/c92268386d265042ea16)です。しかし、大量の文字列を配列に入れるのが面倒だったので `range` 関数のマルチバイト版を探していたら GitHub の gist で公開されていた方いらしたたので利用させていただきました。

- [mb_range](https://gist.github.com/rodneyrehm/1306118) by Rodney Rehm @ Gist
- [MD5 ハッシュ関数を10桁に短く漢字で圧縮](https://blog.keinos.com/20170720_2810) @ KEINOS blog


```php:MD5の値を10桁に圧縮する
<?php

require('mb_base_encode.php');
require('mb_range.php');

// Input string
$string = 'This is a sample string to be hashed.';

// Output
echo 'Original String: ', $string, PHP_EOL;
echo 'MD5 hash raw   : ', hash('md5', $string), PHP_EOL;
echo 'MD5 hash short : ', mb_base_encode(hexdec(hash('md5', $string)));
```
```text:出力結果
Original String: This is a sample string to be hashed.
MD5 hash raw   : baad33e1e97f316b9750c27c86bf64d6
MD5 hash short : 䙔倁屩劷䋾彨䏔䂌䤘剒
```

```php:mb_base_encode.php
<?php

/**
 * Returns base Nth encoded string from decimal number input.
 *
 * @param  integer $number Decimal number to encode
 * @return string          Encoded string
 */
function mb_base_encode(integer $number): string
{
    $char = array_merge(
        mb_range('䀀', '䶵'), // U+4000 - U+4DB5
        mb_range('一', '俿'), // U+4E00 - U+4FFF
        mb_range('倀', '忿') //  U+5000 - U+5FFF
        // REF: http://www.utf8-chartable.com/unicode-utf8-table.pl?start=16384&number=1024
    );

    $base   = count($char);
    $result = "";
    while ($number > 0) {
        $result = $char[ fmod($number, $base) ] . $result;
        $number = floor($number / $base);
    }

    return ( $result == "" ) ? 0 : $result;
}
```
```php:mb_range.php
<?php

function mb_range(string $start, string $end): array
{
    // if start and end are the same, well, there's nothing to do
    if ($start == $end) {
        return array($start);
    }
    
    $_result = array();
    // get unicodes of start and end
    list(, $_start, $_end) = unpack("N*", mb_convert_encoding($start . $end, "UTF-32BE", "UTF-8"));
    // determine movement direction
    $_offset = $_start < $_end ? 1 : -1;
    $_current = $_start;
    while ($_current != $_end) {
        $_result[] = mb_convert_encoding(pack("N*", $_current), "UTF-8", "UTF-32BE");
        $_current += $_offset;
    }
    $_result[] = $end;
    return $_result;
}
```
- [オンラインで動作をみる](https://paiza.io/projects/iiDHv_kiNWGSCFXkUtMmgw) @ paiza.IO