2013年01月20日

【PHP】重み付き乱数を取得するロジック

仕事をしている合間にちょっとした事を検索して、
将来的に使えそうなプログラムロジックなんかを探しては、
会社で自作しているライブラリに組み込んだり、スクラップブックで保存していたりします。

前回は重み付きランダム結果を取得出来るというロジックがヒットしたので、
「前に一度使用した事があるし、とりあえず保存」
と保存して動かしてみると、
どうもでてくる結果がおかしい。
で、ロジックを調査してみたら、処理ロジックが間違っているようだったので、

(たぶん)正しく動作するように作り直してみました。


$wlist = array(1,2,3,4,5,6,7,8,9,10);
shuffle($wlist);
// 1〜10の合計は55なのでn*1000に収束させるため55000回ループさせる
$result = array();
for($i=0; $i<55000; $i++){
$key = weighted_random($wlist);
$result[$key]++;
}
// view
ksort($result);
foreach($result as $key => $count){
echo "[$wlist[$key]] $count" . BR;
}


//---------------------------------------------------------------
// functions
//---------------------------------------------------------------
function weighted_random($weights){
list($lookup, $total_weight) = calc_lookups($weights);
$r = mt_rand(0, $total_weight);
return binary_search($r, $lookup);
}

// $lookup:$weights を加算していった配列
// たとえば$weights={5,2,8,10}だったら、$lookup={5,7,15,25}となる
// こうすることで、weghts 配列をソートすることなく、昇順の配列が得られる
function calc_lookups($weights){
$lookup = array();
$total_weight = 0;
$pos = 0;

foreach($weights as $val){
$total_weight += $val;
$lookup[$pos++] = $total_weight;
}
return array($lookup, $total_weight-1);
}

// 二分探索法
// なので、オーダーがO(n)からO(log2(n))になる
function binary_search($needle, $haystack){
$right = count($haystack)-1;
$left = 0;

// 左を示すポインタ($left)が右を示すポインタ($high)と同じ値か、
// 大きい値となったとき、配列に対する$needleの相対位置が特定される
while ( $left < $right ){
// (int)をつけることで$midを整数値にする
$mid = (int)(($right + $left ) / 2);
if ($needle >= $haystack[$mid]){
// 右半分へ
$left = $mid + 1;
} else if ($needle >= $haystack[$mid-1]) {
// ぴったり
return $mid;
} else {
// 左半分へ
$right = $mid - 1;
}
// $haystackの中に$needleがない場合は、
// この時点で$left > $rightになるのでループから抜ける
}
return $left;
}

参照先:http://blog.image-lab.net/2012/07/php_20.html


参照先のコードと自分のコードは細かいところで汎用性が減っていたり、コーディング規則が違ったり、処理ロジックで違うものを使っていますが、
元コードは2箇所ほど決定的に間違っているようです。
どこがどう違うのか読んで見るのも面白いです。

2012年12月06日

【PHP】グローバル浸食 $GLOBALSにおけるシステムが吹っ飛ぶレベルの危険な仕様

寝ようとしている時にふとPHPの仕様レベルの設計で引っかかって半日くらい原因解明にとられた事を思い出しました。

で、この仕様ってPHP4で出たのは覚えているのですが、
PHP5になっても同じ仕様だったような気がしていたすごく気になったので、
日付が変わった時間にパソコンを付けてチェックコードを出して見た訳なんですが、

PHPの恐るべき恐怖がここに詰まってる

すごいなぁ。
おそろしいなぁ。
PHP5でも動くってことは、これは完全にPHPの仕様としてのシステムなんだよなぁ。



問1.下記のコードにて出力されるメッセージを答えなさい
$dir = 'directory path';
echo $dir;
まぁ、これはそのまま
directory path
と出力されます。



問2.
$GLOBALS['dir'] = 'BaseDir/';
$dir = 'directory path';
echo $GLOBALS['dir'] . $dir;
このソースコードの結果は
directory pathdirectory path
となります。
BaseDir/directory path
とはなりません。



初心者どころか、PHPでメシ食ってるような人でも発生させてしまう危険性のある
グローバル浸食という現象です。
※それっぽいワードが見つからなかったので自分で名付けてみましたw

ちなみに$GLOBALSで始まる配列はグローバルスコープと呼ばれる物でして、
「関数とかクラスの外で定義した変数を関数とかクラスで使いたいです」
「でもglobalでいちいち変数を呼び出すとか、変数を侮辱してるみたいで他の言語でも嫌われてアンチが沸くような記述だからやりたくない」
「defineは変更出来ないから、変更出来なきゃダメダメです」
「だったらこの$GLOBALS変数で一気に解決よ」

というような使い方をされる事が多いです。


多いんです。


小さいシステムの上ではdefineだったりするのに、
大きいシステムでは、固定値の一括管理のつもりなのか、
$GLOBALSにデータベースのパスワードとか、ディレクトリのなんたらの情報とか、ライブラリで使用する設定用配列とか、
グローバルという名前が付いているからという理由で
重要度があって尚かつ関数やクラス内などあらゆる場所で使われる可能性があるデータなんだし使っちゃおう
といった感じに使われる事がちょこちょこあります。



で、$GLOBALSの動きをちゃんと説明しますと、
$GLOBALSはPHPのソースコード内で定義されるすべての変数を保持する変数と言う意味合いを持っています。
ですのでvar_dump($GLOBALS)でいろいろと中を覗いてみてください。
$_GETや$_POST、$_SERVERのような定数みたいなもんだと思っていた変数が
['_GET'] ['_POST']のように連想配列で格納されています。
もちろん['GLOBALS']と自分自身も格納しているわけです。

var_dumpの後ろで一つ変数を作ってみてください。
そうすると$GLOBALS['今作った変数']というような項目が増えます。



大体この動きで理解出来たと思いますが、
PHPで使用されるすべての変数は$GLOBALSから使用も変更も出来るし、
$GLOBALSの中にある変数も$・・・という物からアクセスする事が出来ます。
※関数やクラスの中で作られる変数は若干特殊な為、グローバルスコープからは見えません

故にグローバルだからという理由だけでグローバル変数に大切なデータをつっこんでおくと、
うっかり普通に定義した変数の名前を被らせて、そのまま後の処理でめちゃくちゃなバグが起こる危険性があります。



グローバルに使いたい定数はちゃんとdefineで定義しておきましょう。






あっ、ちなみに私はグローバルスコープとかに普通に定数とかつっこんじゃってますw

PHPの変数名には使用出来ない文字ってのがあるので
$GLOBALS['@dir']のように変数名に使えない@から始まる名前を与える事で、
グローバル浸食の発生を押さえ込む事が出来ます。

defineは確かに強固だけど、データによっては配列として保持しておきたいじゃないですか。
とあるライブラリを狙い通りに起動させる為には配列の定数を入れることできっちり動くってパターンもあるんですよ。

そういう理由もあって
固定値でしか使用しないものはdefine
配列として置くと後で見やすい物はGLOBALS
と使い分けていたりします。


どうしても$GLOBALSにデータを突っ込んでおきたい場合は
 $GLOBALS['@〜']な名前で定義すればグローバル侵食は起こらない!

2012年08月26日

【PHP/Smarty】Fatal error: Class 'Smarty_Internal_TemplateCompilerBase' not found in /usr/local/apache/www/htdocs/prestadev.pl/tools/smarty/sysplugins/smarty_internal_smartytemplatecompiler.php on line 127

PHPベースでちょこっとフレームワークを自作してたら、
下記のようなエラーが出てきて、半日開発が止まりました。
Fatal error: Class 'Smarty_Internal_TemplateCompilerBase' not found in /usr/local/apache/www/htdocs/prestadev.pl/tools/smarty/sysplugins/smarty_internal_smartytemplatecompiler.php on line 127


というか英語サイトとかばっかり出てくるのに、
これだ!という解決作がどうしても見つけられなかった訳なんですよ。

で、細かくいろんな条件を変えたりして、時にはSmartyのキャッシュに惑わされたりして、
私の環境ではある特定条件下で発生する事が発覚しました。

【条件と回避方法】
1.Smartyのバージョンが3.xである事
 Smartyのバージョンを2.xにダウンさせる事で、このエラーは出なくなります。
 バージョンダウンして問題無いのであれば、こちらをおすすめします。
 多少の性能ダウンは起こるかもしれませんが、
 このバグを回避する手間暇を考えると、おそらくすごく楽です。

2.__autoloadを使っている
 index.phpからClassLoader機能を持つファイルをインクルードして、
 Smartyをインクルードするような処理が部分的にありました。
 ちなみにSmartyをインクルードする部分だけをautoloadを一切返さずに読み込む事で、
 なぜか回避する事が出来ます。

 私のソースコードでは、
 Smartyを継承しているクラス(SmartyControl)をautoloadで読み込み、
 SmartyControl.phpの先頭でダイレクトにSmarty.classを読み込むソースです。
 このソースコードにて、
 SmartyControlをautoloadを使わずにダイレクトインクルードする事で
 なぜかこのバグは発生しなくなりました。

 見た感じ、autoloadでクラスを読んだ場合に、
 さらにその中でincludeが発生すると、
 普通にincludeを読み込むのとは少し違う動作をしているっぽいのですが、
 残念ながら具体的に何が原因でバグになっているのかは分かりませんでした。

【結論】
Smarty2.xに置き換えるか、
Smarty周りのインクルードを単純なインクルードに差し替えてしまえば、
高い確率で回避する事が出来る。
これ以上調べるのは骨が折れるので、手を出したくない。

2011年11月15日

【PHP】Javaで言うClass#newInstanceみたいに、クラスを作成する

PHPでクラスを生成して、
それをどこかの関数かクラスに渡して、
その中でクラスのコピーを量産したい。

そんな状況が必要になりました。

単純にクラスをコピーする場合
// PHP5以上
$newClass = clone $baseClass;

とやると普通にコピー出来ます。



いや!違う!
コピーはコピーだけど、
これでコピーしたら、$baseClassにいろいろ詰め込んだ変数とかも全部コピーされちゃうじゃないか!
今欲しいのは空っぽのnewしたてのようなコピーだ

意外な事に、
PHPって定義したクラス変数を他のデータに渡して、
型の定義を深く知らなくてもいい、
オブジェクト指向な事をしようとすると、
クラス名を知らなくては、newで新しく生成する事が出来ないわけです。



と言う事で、クラス名を取得して、
そのクラスを新しく定義するにはこんな感じで定義する事が出来ます。
$className = get_class($baseClass);
$newClass = new $className();


人によっては若干気持ち悪いかもしれないですけど、
変数(可変変数によるクラス定義)を使う事で、
クラスをコピーする事が出来ます



実はもっと簡単にさっくり単純にJavaのnewInstanceみたいな形でクラスが作れたりする訳なんです
$newClass = new $baseClass;

いや、それでいいのか?
ってくらいに簡単で単純だけど、
PHPならこれで新しいクラスが作れる。
※クラスとか関数の中でOOP的にクラスの型を知らなくても、なそんな時に役立つ



ついでにクラスの中で良ければ、
class xxxClass {
public static factory(){
return new static;
}
}
$newClass = xxxClass::factory();

という、スタティッククラスの中でよければ、こんなnewでクラスを作る事も出来ます。
これは完全にファクトリークラスを作る時とかに非常に役に立ちます。
特に継承とかを前提にすると、
Javaよりやりやすい。
※ファクトリークラスとかで役に立つ

2011年06月28日

PHPの開発効率をさらに上げる為の小ネタ

PHPの開発効率を上げる10個の関数
http://d.hatena.ne.jp/haru-komugi/20090818/1250561641

現にこれに近い事を会社の開発でちょこちょこやってたりする。

これ以外にも会社で新しいプロジェクトの時に
私が使っているちょっとした技法を上げてみる




.htaccessに追加する
php_value auto_prepend_file
詳しいこれの使い方はauto_prepend_fileで検索すると分かるのですが、
要はURLでアクセスしたPHPの前にauto_prepend_fileのPHPロジックが勝手に動くという設定が増えます
フレームワークを利用している場合、競合する事もあるかもだけど、
これは覚えておくと、PHPで新規ファイルを追加する時などに非常に便利になります

私はphp_value auto_prepend_file E:/localhost/Net/setting/base.php
と呼び出して、
さらにbase.phpで$GLOBALSの中にディレクトリのファイルパスやdefineで固有の文字列の定義をしてます
開発効率を上げる10個の関数のいくつかをbase.phpの中に置いておけば、
includeを使わなくても呼び出せるようになりますので、
PHPがほどよく分かってきましたって人は
とりあえず、これを使ってみるといいってくらいにおすすめ



define('BR', "<br>\n");
デバッグする時にループの中に強引にechoを追加する人も多いと思う
とりあえず、サクッと改行したい。
HTMLもあるんで<br>は必須なんです
そんな人はとりあえず、これを追加しておく
そして
echo $value . BR;
と書いておけば、
まぁ、ほどよく 便利になったと思えるんじゃないかな



ローカル上で作ってサーバに上げる際に、一部ファイルを修正する事がある
if($_SERVER['SERVER_NAME'] === 'localhost'){
$GLOBALS['@dir']['base'] = 'E:\localhost\Net\\';
$GLOBALS['@dir']['url'] = 'http://localhost/Net/public/';
define('debug', true);
}else if($_SERVER['SERVER_NAME'] === 'straypas.mistysky.net'){
$GLOBALS['@dir']['base'] = '/home/mistysky.net/xxxxxxxxx/public_html/';
$GLOBALS['@dir']['url'] = 'http://straypas.mistysky.net/';
define('debug', false);
}

$_SERVER['SERVER_NAME'] でローカルホストの名前が取れるんだから、
こういうデータは、サーバとローカルでは違う動きをしてしまう所で使うべき
urlのデータを振り分けてグローバルに入れておいたり、
ディレクトリの違いをグローバルに入れておいたり、
ここでデバッグ用の固定値を書き換えたりと
いろんな事が出来る
出来ればここで読み込むファイルを分岐させておくべき
ってくらいに、いろんな事が出来る

ちなみに私は$GLOBALS['@dir']という少しマニアックな方法を使っています
これの詳しい理由に付いては別記事にて書いています
http://white-mode.seesaa.net/article/211853226.html

defineじゃなくて$GLOBALSを使う理由は大きい意味は無いのですが、
個人的にはこっちの方が好きなので、
こっちを良く使っています



残念ながら10個も技法持ってないので、
ほんのちょこっと

2011年05月12日

ビット演算の利点と効果的な使い方

ついったーで特定のビット項目を取り出す事の利点が分からない
というつぶやきを見たので、
大ざっぱに並べてみる



<前提:2進数を理解している事と簡単なプログラムは書ける事>



<ビット演算にて特定のビット項目を取り出すには>
:int a = (適当な数値);
例えば数値の2ビット目のビットがONかOFFかを確認するには
a & 0x2
という計算式になります
3ビット目なら
a & 0x4
4ビット目なら
a & 0x8
とそのビットの項目の数値を&で噛み付かせます

一応ですが、
&はビット演算子で右と左の数値をビット配列に直し、それぞれANDをとって、その結果を返す
0xは16進数として表記する時に数値の頭に付ける
なので0xの後ろは0〜fの数値を書く事が出来ます



<ビット演算の特徴>
計算も記述も複雑で使い所が見えにくいかもしれませんが、
ビット演算はコンピュータ的にはものすごく単純な計算式になるため
(普通使える演算子の中では最も機械語に近い属性)
処理速度が非常に速くなる傾向があります
もちろん、コンパイラなどの性能にもよるのですが、
ほとんどのプログラミング言語で
数倍の速度で動くので、
画像を加工したり、暗号化みたいな複雑で煩雑な計算式みたいに
処理に時間がかかる処理を部分的にビット演算する事が多いです。

また、メモリ使用量も少ない傾向があるので、
マイコンのように非常にシビアな所でも
わざわざビット演算で計算式をやったりするようです



<普通のプログラミングでもビット演算は使います>
上記の説明だけだと、
「自分には関係ないな」
と思う人も多いでしょうが、
ビット演算にはもっと効果的なやり方があります。
それはboolパターンを組み合わせると言う事です

例えば、ゲームのデバッグを設定する為のメソッドを作ります。
デバッグと一口に言っても、
・フレームレートを落としてゲームを
・当り判定を表示されるようにして、ちゃんと効果が出ているか見る
・キャラを無敵状態にする
・ステージセレクトが自由に出来るような項目を出す

と、デバッグにもいろいろとあります
で、この項目の場合、
当り判定(敵から味方)とキャラ無敵は同時に使ってもほとんど意味がないです
(敵の攻撃受けないデバッグ設定なのに、敵の当り判定を見ても大して効果ないです)
だから、全部を一度に呼び出す必要は無いですが、
この複数の項目からいくつかを呼び出す事にはなります

で、例えば
setDebugFramerate(boolean);
setDebugHitview(boolean);
setDebugMuteki(boolean);
setDebugStageAll(boolean);
setDebugHogehoge(boolean);


とデバッグの数だけ設定を作った場合、
これから3つ呼び出す時は3つの項目をソースで呼び出す必要があります。
もし、10個のデバッグ項目を呼び出そうとしたら10項目呼び出さなければいけません。

これをビット演算を利用する事でこうなります
setDebug( DEBUG_FRAMERATE | DEBUG_HITVIEW | DEBUG_HOGEHOGE );


3項目呼び出さなきゃいけない所が1行で
しかもシンプルになりました。

ちなみにこのDEBUG_xxxは定数で
0x0001,0x0002,0x0004,0x0008,0x0010,0x0020・・・・
という数値が振り当てられていて
setDebugの内側では
void setDebug(int debugType){
 if(0 != (debugType & DEBUG_FRAMERATE)) setDebugFramerate(true);
 if(0 != (debugType & DEBUG_HITVIEW)) setDebugHitview(true);


となっているので、
定数をor演算子で組み合わせるだけで、
複数の設定を一気に済ませる事が出来ます。



とりあえず、
サンプルみたいな簡単な物ばっかり作っていると機会があまりないけど、
他人にもライブラリとして提供するソースコードや
ゲームみたいな大きめな物を作るときに
こういうやり方を一つ覚えておくと、
ソースコードが綺麗にスッキリしたものになりやすいです。

2010年12月17日

初めてのAndroidNDK JNIを組み込んで高速化

仕事でJavaの速度だけだと及ばない所がどうしてもあったので、
クリティカルな処理の部分をC++で書いて、高速化

で、その過程で
とんでもなく行き詰まったりしたので
とりあえず書き込んでおく





本日の追加アプリ
・Cygwin
 Windows上でUNIXコマンドが使えるようになるアプリ
 ちょっと特殊な事は出来ないようだけど、
 基本的な機能は一通りそろっているし、Cygwin上からWindowsディレクトリのファイルも利用出来る、
 意外と優れものアプリ
 UNIXコマンドの中にgccなどもインストール可能なので、
 Lunuxの勉強に興味がある人や、C言語の勉強をしたいという人にもお勧め
・Android NDK Revision 5
 今月公開されたばっかりの新作
 Android Frameworkでアプリ組んでるけど、どうしてもJavaじゃ速度が足りない
 そんな人が行き着く、アプリケーション
 ちなみにAndroid2.2でDalvikが大幅更新され、
 Javaの性能を最大限まで引き出すようなシステム改革がなされたので、
 これが最大限に役立つのは、2.2以前か、かなり複雑な処理を要求されるシステムである可能性が高い





ちなみに今回はつい最近出たNDKr5という最新型だったことや、
JNIの経験がなかった為に、苦戦したことから書き出します



1.AndroidNDKをインストールする
 インストールというか、解凍して適当なところに置くだけだったから、
 適当なところに置くだけ

2.Cygwinをインストールする
 検索でCygwinのインストールみたいに検索すると出てくるところでそのままインストール手順習えばOK
 インストールも簡単でした。
 
・makeとかgccとかインストールし忘れた
 ・cygwin.exeをインストールしたときと同じような手順で動かす
  パッケージインストールの所で追加するものを選べば普通に入った

3.Cygwinを設定する
 <インストールPath>\cygwin\home\<ユーザーID>\.bashrc に
export ANDROID_NDK_ROOT=<1のAndroidNDKのディレクトリをwindowsのパスで書く>
export PATH=$PATH:/cygdrive/<1のAndroidNDKのディレクトリを↓>
// ただCドライブだと /cygdrive/c/〜 Dドライブだと /cygdrive/d/〜 みたいに書くのが正解みたい

・AndroidNDKのコマンド
 AndroidNDKのバージョンによる違い
  大きく変わったわけじゃないが、
  少なくてもr3基準で解説しているサイトを眺めながらr5のインストールをやろうとしたら
  2倍は混乱出来るので
  r5のサイトで分かりやすいサイトが見つからなかったら、r4のサイトを参考にしてもOK
 
 ・「bash ./build/host-setup.sh」
  このコマンドは必要なくなりました
  セットアップってやつをやらなくても使えるようになっているので、いらない。
  探したり、悩んだりしないで、スルー推奨
 ・「make APP=<ここにインストールするパッケージ名が入る>」
  このコマンドも廃棄
  r5でコンパイルするときはこれじゃなくて、「ndk-build」だけでOK
  ちなみにこのコマンドを発行する場所はAndroidManifest.xmlを置いてあるディレクトリでOKみたい
  (C++のコードが階層化されたら、少し変わるのかもだけど。まだ未確認)

・一通りインストール完了したので、ちゃんと動くかのテストをする
 AndroidNDKに一緒に入っていたhello-jniを動かしてみる
 1.Cygwinでhello-jniのディレクトリにアクセス
   ndk-buildコマンドを入れると、hello-jniのC++コードがコンパイルされる
 2.eclipseでhello-jniを追加する
  AndroidNDKのディレクトリからhello-jniを探す
  ・1の課程を飛ばした場合
   まず1の課程を行って、hello-jniをクリーンする
 3.追加したパッケージをそのまま動かす
  ・たぶん動かなかったら、1−2のやり方がおかしかったか、もうちょっと違う所が間違ってる
   まぁ、私はここで引っかからなかったので、原因は想像出来ない
 4.hello-jniのC++のソースコードを少し書き換える
  ・とりあえず出力する文字を少し直して、
   1のコンパイルとクリーンをすると、
   ちゃんと変化が反映される。
 5.実際にJNIの入ったプロジェクトを作成する
  ・hello-jniを真似て、ベースのクラス、C++のNativeコードを書く
   C++のコードはJNIというディレクトリを追加して、
   Android.mkファイルを一緒のディレクトリに置く
  ・1のやり方でコンパイル
   この際にlibsみたいなディレクトリとか、.soファイルとかいくつか出来るけど
   気にしない。
   プロジェクトの中に無くても、心の中にはあるから
  ・やっぱりここでクリーンしてから、端末に入れる



とりあえず、コンパイルする方法とクリーンする事を忘れなければ
NDKは簡単に組み込める



それと、CygwinでGitもインストールしておいて、
GitからAndroid本体のコードをダウンロードしておくと、
android2.1で2.2のFramework機能を呼び出せたりということも出来るっぽい感じ
FrameworkにはC++のコードが含まれていたりするから、
NDKがあればそんなことも可能らしい



これも読んでおくと、Androidに詳しくなれる リンク
・Android NDKを使う(アプリの高速化)
 私はこれを見ながらNDKを動かせるようにしました
 http://techbooster.jpn.org/environment/824/
・5倍高速なAndroid 2.2,速さの理由に迫る
 http://gihyo.jp/lifestyle/serial/01/t-mobile/0036

2010年03月08日

【PHP】ランダムな文字列を生成する

仕事でランダムな文字列を作ってDBに保存する
っていうような事が必要になった時に作った物の応用




// $flg :: 1:数列 2:小文字アルファベット 4:大文字アルファベット
// ビット演算のORで指定する
function rand_string($length,$flg=7,$base_string=''){
$rand_string = '';
$length = (int)$length;

if(1 === ($flg & 1)){
$base_string .= '1234567890';
}
if(2 === ($flg & 2)){
$base_string .= 'abcdefghijklmnopqrstuvwxyz';
}
if(4 === ($flg & 4)){
$base_string .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
}
$chars = preg_split('//',$base_string, -1, PREG_SPLIT_NO_EMPTY);
$chars = array_values(array_unique($chars));

$count = count($chars) - 1;
for($i=0; $i<$length; $i++){
$rand_string .= $chars[mt_rand(0,$count)];
}
return $rand_string;
}


ちなみにこのコードを書く際にいくつかのサイトを参考にしたけど、
最も参考になったサイトはとっくに潰れていたりします。

ちょっと解説を

rand_string($length,$flg=7,$base_string='');
$length 取得したい文字数を指定する
$flg ビット演算を利用して取得したい文字を指定する
  1なら数列、10なら小文字、100なら大文字で乱数を形成してくれます
$base_string そのほか、乱数の中に記号や特殊文字が欲しい場合に書いてください
※ちなみに他の人のやり方を見てると
switchやif、大小英数字固定の関数とかが結構ありましたが、
ビット演算が好きなので、こういうコード

$chars = preg_split('//',$base_string, -1, PREG_SPLIT_NO_EMPTY);
文字列を一文字ずつに区切って配列にする(空白の配列を作成しないバージョン)
詳しい事は自分で調べろって離しですが、
文字列を一文字ずつに区切って配列にしました
理由は次の処理

$chars = array_values(array_unique($chars));
1.array_unique これに配列を渡すと、重複している値を削除してくれます
 ユーザ指定の追加文字列の中に英数字とかが入っていると、特定の文字だけが出現率が高いという事が起こるので、一応除去してみた。
 特定の文字の出現率が高いランダム作りたいなら、これ消しちゃえばいい
2.array_values 配列の値だけを全部取得してくれます
 array_uniqueは削除する際にキーのリセットを行わない。
 つまり?
 配列が[0] [2] [3] [4] [5] [6] [7] [8] [9]・・・・
 みたいな歯抜けな配列になったりする。
 その後の乱数処理の結果が狂ってしまうので、array_valuesでキーをリセット
 (※array_values使う処理とかレアだと思います)

$count = count($chars) - 1;
配列に入る前にカウントは済ませておきましょう。
forの中でcountを使うなんてもってのほか、
超弩級のボトルネックになって、処理速度が落ちるとか、あったりします。
マイナス1は、配列の0〜x番目が欲しいから。
countはx+1の値が出てきますので

for($i=0; $i<$length; $i++){
$rand_string .= $chars[mt_rand(0,$count)];
}

forは見ての通り
mt_randは0〜$countまでの乱数生成器。
そして .= で文字の追記
whileを使ったり、インクリメント(++)を変数の前に持ってくる方が、若干処理速度が高くなるっぽい。
だけど、妙に冗長的になりそうな雰囲気もあるので、使用しない。
ちなみに出来るだけ高速化するには、
ループの中での計算量を出来るだけ小さくする必要がある。
仕事でやってるとめんどくさいから、怠けたりするけど。

文字列の何文字目を抜き出すってやり方もあるけど、
文字列のx文字目を抜き出すやり方よりも、配列をそのまま使った方が早い事が分かった。
(たぶん、メモリには若干優しくない様な気はしますけど)



検証結果として、
ネット上に転がってる同じような処理をする関数の処理速度を調べて回りましたが、
検証した中ではこの関数が一番早いです。

※一部、バグの可能性がある処理がこの関数の中にありますけど、私はそういう使い方しないから、問題なし!
PHP4.2より古いとmt_randの乱数生成がされないせいで変な結果が出る。
ただ、その環境が無いので確かめられてないですけど。




ちなみに仕事で作ったこの関数ですが(一部修正あり)
実際の所、uniqidで十分事足りました。

2009年12月23日

【PHP】クエリーを自由自在に操る技

1.GETのクエリー文字列を取得する
hoge.php?a=b&c=d
というような部分の「a=b&c=d」を取得する。
echo $_SERVER['QUERY_STRING'];

GETを取得する時はサーバ関数にあるんでそこから引っ張り出してくる



2.POSTのクエリー文字列を取得する
データをPOSTで送信する時に、
Content-LengthとHTTPのデータ部分に送信するデータ流し込んでるもんなんです。
よく分からない人はFirebugをONにしてPOST送信、接続→送った所のアド→ヘッダ+POST
を何となく眺めてみるべき。
echo file_get_contents('php://input');

GETとは違う。
PHPの設定でどっちもサーバ変数で取得出来てもいいと思う。



3.「1、2」で取得したクエリーの文字列を配列にする
$_GETとか$_POSTみたいな配列の固まりをPHP関数で作ってみよう、という話
parse_str($_SERVER['QUERY_STRING'],$get_array);

$get_arrayに変換された配列が入ります。
返値にはboolで結果だけですので、



4.「3」や$_GET,$_POSTの配列を文字列に直してみる
3の逆の作業です。
echo http_build_query($_GET);

3と違って返値なんです。
クラスが入ってくると無茶だけど、多重配列なら利用出来ます。





こういう処理ってのは、
Pagerみたいな検索結果関係を操作したりする際の
細かい部分を処理する際にこういうのは非常に便利です。
覚えておけば、意外と使う。

このあたりの関数を使えば、
うちのサイトに来た人が、どんな検索ワードでアクセスしているのかを取得して、
その検索ワードを分析する事だって、
結構簡単に出来ちゃいます
posted by 迷い猫 at 20:32| 埼玉 ☀| Comment(0) | TrackBack(0) | 専門書店(プログラミング言語) | このブログの読者になる | 更新情報をチェックする
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。