この記事が属しているArticlesというコンテンツ内では各記事のIDの末尾がアルファベットになっているため隣り合う記事を走査する為には隣り合うアルファベットを求める必要がある。PHPには次のアルファベットを求める関数がないので自前で実装してみることにする。
「baa」といった複数桁のアルファベットに対応するため今回は1度数値に変換してから隣り合うアルファベットを探すことにした。繰り上がりなどに対応するには1度数値に変換した方が簡単である。配列との相性を考えて、0から数えて「その文字が何番目に出てくるか」を求める。
n進法と違い、純粋にアルファベットだけで表される数値は「0」の扱いが難しいので注意が必要だ。例えば10進法で「00」は「0」と等価だがアルファベットだけの時の「a」を「0」の様に扱うとしても「aa」と「a」は等価ではない。「a」は0だが「aa」は26になる。逆に「1」の様に扱うと16進法の「b0」の様な1桁目が0の数値が表せなくなるため、実装の際には1桁目と2桁目以降の処理を変える必要がある。
//-------------------------------------------------------------------------
// int str_to_num( string code )
// codeは任意の文字列。引数が扱えない値の場合FALSEを返します。
//-------------------------------------------------------------------------
function str_to_num( $target ) {
// アルファベットの定義
$alphabet = array(
"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",
);
// 変数の方を調べて不正な値を弾く
if( ! is_string( $target ) || $target == "" ) {
return FALSE;
}
$length = strlen( $target );
for( $i = 1; $i <= $length; $i++ ) {
// 末尾の1桁を取り出して
$one = substr( $target, - $i, 1 );
// 数字に変換。この時aが0から始まることに注意
$num = array_search( $one, $alphabet );
if( $i == 1 ) {
// 1桁目はa=0なのでそのまま
$result = $num;
} else {
// 2桁目以降はa=1なので1足してから桁を掛ける
$result += ( $num + 1 ) * pow( 26, $i - 1 );
}
}
return $result;
}
1桁目の「a」は「0」だが、2桁目以降の「a」が「1」として扱われている事に注意。そのまま「0」としてしまうと「ab」が「0 * 26 ^ 0 + 1 = 1」になってしまうので「b = 1」と等価になってしまう。本来なら2桁目以降の「a」は繰り上がりの分なので「1」とするのが正しい。
//-------------------------------------------------------------------------
// string num_to_str( int number )
// numberは任意の整数。
//-------------------------------------------------------------------------
function num_to_str( $target ) {
// アルファベットの定義
$alphabet = array(
"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",
);
// 不正な引数を弾く。負の値は無効。
if( ! is_numeric( $target ) || $target < 0 ) {
return FALSE;
}
// まず、1桁目。a=0なのでそのままmodを求める
$one = fmod( $target , 26 );
$result = $alphabet[ $one ];
// 1桁目を引いた余りから繰り上がり分を求める
$carry = ( $target - $one ) / 26;
// 繰り上がりが無くなるまで繰り返し
while( $carry != 0 ) {
// a=1なので1引いてからmodを求める。
$one = fmod( $carry - 1 , 26 );
$result = $alphabet[ $one ] . $result;
$carry = ( $carry - 1 - $one ) / 26;
}
return $result;
}
こちらもやはり2桁目以降の処理に注意が必要だ。
以上の2つの関数を使って隣り合うアルファベットを求める関数を作る。今回は少し汎用性を持たせるため隣だけではなく、任意の間隔を設定できるようにした。
//-------------------------------------------------------------------------
// string relative_str( string base [, int distance, bool overflow ]] )
// baseは任意の文字列。distanceは移動距離、overflowは桁溢れに対応するか。
// 省略された場合、それぞれ1、TRUEが初期値になります。
//-------------------------------------------------------------------------
function relative_str( $str_base, $distance = 1, $option = TRUE ) {
// 文字列を数値に変換して距離を足し、条件に合わせて戻り値を返す
$num_base = str_to_num( $str_base );
$num_target = $num_base + $distance;
if( $str_target = num_to_str( $num_target ) ) {
if( ! $option && strlen( $str_target ) > strlen( $str_base ) ) {
return FALSE;
} else {
return $str_target;
}
} else {
return FALSE;
}
}
与えられたアルファベットを1度数値に変換し、距離を足してから再びアルファベットに戻している。1度数値にすることで繰り上がりに悩む必要がない。3つ目の引数を「FALSE
」にしておくと桁溢れを起こした場合に「FALSE
」を返すようになる。初期値の「TRUE
]だと「zzzz」の次は「aaaaa」の様に勝手に桁を拡張してくれる。尚、間隔には負の値も設定できるが最終的に「a」より下の値になると「FALSE
」になる。