yamicha.com's Blog

アガルリスク
2006/04/03

 面白いというか、笑えないというか、とんでもない記事がありました。
 何でも特定のアガリクス製品を食べるとガンになりやすくなるそうではありませんか。「ガンを消す」などとは大嘘で、摂取すると逆に発ガンリスクが増えてしまうのです。その他のアガリクス製品については、発がん性との因果関係は見つからなかったものの、ガンを治癒するなどという効果はなかったとのこと。
 つまり、アガリクスがガンに効くなどとは大嘘なのです。何か体に良い成分が含まれている可能性はありますが、そんなものは所詮「栄養補助食品」であって、ガンが治癒するなどとは夢物語です。アガリクスなんざ飲むのなら、特定のガンに効果があることが立証されているコーヒーを摂取する方が余程有用です。
 世の中、こういう類のものが結構氾濫しているものです。エセ科学といえば血液型だの何だのに関してもそうですし、成分に関してはマイナスイオン、アルカリ、アガリクス、クロレラといったものにはこれといった効果はありません。実際、そういう小難しい横文字などなかった時代の人々が、120歳まで生きているではありませんか。
 結局のところ、私にはコーヒーすすっている程度がお似合いなのでしょうが。何か色々と知られざる効果があるようです。
 ついでにタバコに関しては、ニコチンに抗ガン成分を抑える働きがあることが明らかになったようです。つまり、これで薬も効きづらくなり、あらゆる面からタバコはガンの原因になるということです。
 そういえば成人式の時、「俺はタバコで税収に協力している」と抜かしたタコがいました。こういうオツムが弱いのが、どうしてタバコを吸うのでしょう。それともタバコのせいでオツムが弱くなったのでしょうか(とある調査で、喫煙者、元喫煙者、非喫煙者の仕事効率を調べたところ、左が最悪で順に良くなったとのこと)。タバコは精神年齢が20歳になってから。日本でタバコは合法ですから「絶対やめろ」とは言えませんが、1本吸うごとに国から金を着服しているという意識は持ってもらわなければ。
 「タバコには害しかなく、他人に迷惑をかけ、国から金も奪う行為です。さらにガンや梗塞などでチューブ生活を送ることにもつながります。やめましょう」。とはいえ、簡単にやめられないのがタバコというもの。パタッと禁煙しなくても、最終的に禁煙できれば病気のリスクはガクッと減ります。逆に「税収に協力している」と称してふんぞり返ってスパスパ人の顔に煙を吐く人間、見かけたら殴っていいのを合法化しませんか?国税を横領した上、他人の健康をも害しているのですから。

 もう何というか、いい加減あきれてきました。前原代表が「情報に金を払うのは違法ではない」と言えば、今度は武部氏が「何たる発言だ」。もう両方ダメです
 前原氏も発言に気をつけなければなりませんし、実際に情報を買っていたら西沢氏は詐欺罪でした。逆に武部氏や自民党もとんでもないワルで、救いようのない本物の詐欺師を候補に立てたことを忘れてはいけません。その上、老人のつつましい老後資金を全部奪い取ったような詐欺師を「追い返すな」とは。
 ではどちらが悪いのか。もう単刀直入かつ少々品のない言い方ですが、はっきり言っておきます。例えば「格差」だの「詐欺師を追い返してはならないくせに転落した一般人は平気で蹴落とす政治」だのによって治安が悪化し、私が心臓をえぐられたとしましょう。またはマンションに潰されて死の間際になったり、スポンジ脳で平衡感覚がなくなって寝たきりになったり。そこで誰に恨み言を吐くか。おそらく最後に「国民の命を詐欺師、建設業者、米国に売った小泉、武部、安倍、そしてメール問題ごときで追及をサボった前原め」と恨み言を吐いて死ぬでしょう。そういうことです。
 自民党3悪の1人、安倍氏も「ポスト小泉」のための勇ましさアピールに懸命です。北朝鮮に怒ってみせ、自殺問題で中国に噛み付き、次は靖国批判を批判。特に靖国への異常なまでの執着に関しては、私の予想より安倍氏の頭が弱いのであれば中国の手玉に取られているだけですし、逆に聡明なのであれば、中国の批判を想定して発言し、人気取りをしているのです。
 これが次期首相にふさわしいとは、世も末というか。メール問題ごときで安倍氏と業者の癒着に背を向けた民主党、あなた方にも責任があります。

 そんなこんなでPHP。以前に用いて失敗した「__sleep()」メソッドへの復讐でも。これはシリアル化の際に呼ばれるメソッドで、JavaのwriteObjectに相当します。シリアル化するデータにリソース(データベース接続など)を含めることはできないため、ここで一旦切断などをしておいて、__wake()で接続を行ったりすることになります。
 それでこの__sleep()、そのまま呼んでリソースを破棄すると、一切の変数が保存されなくなります。用いられている全変数名が入った配列を返さなければならないのです。全く面倒なことこの上なし。同じような場合、JavaのwriteObjectではsuper.writeObject()が有効でした。
 とはいえ、PHPではオブジェクトには最初からJavaでいうItarableが実装された状態にあります。foreach()にぶち込めば、名前と値をハッシュにして返してくれるのです。これを利用すれば、以下のようなことができます。
class WakeClass{
	public $one , $two , $three;
	public function __sleep(){
		foreach($this as $n => $v){
			$list[] = $n;
		}
		return $list;
	}
}

// 実装
$w = new WakeClass();
$w->one = 1;
$w->two = 2;
$w->three = 3;

$serial = serialize($w);
$r = unserialize($serial);

print $r->three;	// 3
 別に使わないなら、わざわざ__sleep()をオーバーライドする必要はありません。ただ、クラス変数ではあってもその値に特に意味のない変数(例えばDBなりの接続番号を保持していたり、処理中の一時記憶だけに使っていたり)をシリアル化する必要がない場合、__sleep()でその変数名を除外した配列を返せばOKです。
 後は__isset()やら__unset()やら。
class PDOClass{
	private $db;
	function __construct(){
		$db = new PDO("mysql:host=www.localyamicha.com" , 
			"yamicha" , "password");

		$db->exec("USE yamicha");
		$db->exec("SET NAMES sjis");
		$this->db = $db;
	}
	// テーブルが存在しているかを返す
	function __isset($name){
		$s = $this->db->prepare("SELECT * FROM $name LIMIT 1");
		$s->execute();
		$err = $s->errorInfo();
		if(!$err[1])
			return true;
		return false;
	}
	// テーブルを消してしまう
	function __unset($name){
		$this->db->exec("DROP TABLE $name");
	}
}

$pdo = new PDOClass();
print isset($pdo->table_name);	// 存在すれば1、しないならundef
unset($pdo->table_name);	// テーブルが消えてしまう
 変数に似た感覚でテーブルを扱うプログラムなども書けるわけです。ただ、__setや__get、__callのようなマジックメソッドは混乱を招くため、私としては好きではありません。
 PHPはお手軽な言語ですから、こういう機構も時として使用価値があるのでしょう。例外に関しては、throwsを書かなくても勝手に呼び出し元に順番に送り返してくれるのですが、勝手に送られるというのは危なくもあり。ただ、お手軽なくせにあのいまいましい$thisと$GLOBALSが何とかならないものか。何度忘れてバグを起こしたでしょう。バグの温床です。
 お手軽なくせにDBまで当然のごとく使えてしまうPHP。当然、XA Transactionも(自分でSQLを打ちさえすれば)使えます。例えば世の中にはMySQLとPostgreSQLの両方を提供しているサーバーもあるようですが(私はPostgreSQLを使ったことはありませんが)、こういう場合のトランザクションはXAを使う以外にありません。
 PDOは機能としてはXA Transactionを使えませんが、XA Transactionなんてものは「XA BEGIN...」から「XA COMMIT...」まで打てば動いてくれるものです。例えば次のようなクラスを作れば、一応分散化が可能です。
// XA Transaction 管理クラス
class XATransaction{
	private $db;
	private $brunch;
	private $lock;
	// PDOの配列を渡す
	public function __construct($array = ""){
		if(is_array($array))
			$this->db = $array;
		$this->lock = 0;
		$this->brunch = "com.yamicha.xa";
	}
	// 使用するPDOを登録
	public function addPDO(PDO $data){
		if($this->lock)
			throw new XATransactionException("トランザクション中");
		$this->db[] = $data;
	}
	// トランザクション中ならロールバックして破棄
	public function destroy(){
		if($this->lock)
			$this->rollback();
		unset($this->db);
	}
	// デストラクタ
	public function __destruct(){
		$this->destroy();
	}
	// ブランチ名を変更
	public function setBrunch($b){
		if($lock)
			throw new XATransactionException("トランザクション中");
		$this->brunch = b;
	}
	public function getBrunch(){
		return $this->brunch;
	}
	// 開始
	public function begin(){
		$i = 0;
		foreach($this->db as $data){
			$data->exec("XA BEGIN '$this->brunch' , '$i'");
			$i ++;
		}
		$this->lock = 1;
	}
	// ロールバック
	public function rollback(){
		if(!$this->lock)
			throw new XATransactionException("トランザクション不開始");

		$i = 0;
		foreach($this->db as $data){
			$data->exec("XA END '$this->brunch' , '$i'");
			$data->exec("XA ROLLBACK '$this->brunch' , '$i'");
			$i ++;
		}
		$this->lock = 0;
	}
	// コミット
	public function commit(){
		if(!$this->lock)
			throw new XATransactionException("トランザクション不開始");

		unset($xa);

		$i = 0;
		// 一旦コミットを準備して...
		foreach($this->db as $data){
			$data->exec("XA END '$this->brunch' , '$i'");
			$data->exec("XA PREPARE '$this->brunch' , '$i'");
			$xa[] = $data->prepare("XA COMMIT '$this->brunch' , '$i'");
			$i ++;
		}

		// ここで全部を一気にコミット
		foreach($xa as $t){
			$t->execute();
		}

		$this->lock = 0;
	}
}
// 例外
class XATransactionException extends Exception{
	public function __construct($str){
		parent::__construct();
		$this->message = $str;
	}
}
 PHPにはJavaと違ってスレッドの競合はありませんから、結構いい加減でも動きます。さらに、PDOさえしっかり設定していれば、例外が起こればそれを関数外に投げてくれますから、例外を無視してガンガンコミットをするようなこともありません。
 インスタンスを破棄すると自動的にロールバックがなされます。これは、例えばXATransactionに対して新しくインスタンスを代入し、さらに前に用いた接続をそのまま投げ入れた場合のことを考えた仕様です。この場合にロールバックがされていなければ、重複エラーが発生してしまいます。とはいえ、ユーザーが勝手に「XA BEGIN」と打った上でXATransaction::begin()を呼び出したり、XATransaction::begin()の後で勝手に「XA END」されたりすることに関しては、「それに対しては感知しないからそのつもりで」としかいえないのですが。
 J2EEにはまだUserTransactionがありますが、PHPにそういう機構はありません。さらに、例えばレンタルサーバーでSQLを使うような場合、PHPコード側でトランザクションを管理するしかないわけです。となると、こうして分散トランザクションを管理することが最も安全、ということになるのでしょう。
 肝心の使い方ですが、
$db1 = new PDO("mysql:host=www.localyamicha.com" , "yamicha" , "password");
$db1->exec("USE yamicha");
$db1->exec("SET NAMES sjis");
$db1->setAttribute(PDO::ERRMODE_EXCEPTION , true);

$db2 = new PDO("mysql:host=www.localyamicha.com" , "yamicha" , "password");
$db2->exec("USE yamicha");
$db2->exec("SET NAMES sjis");
$db2->setAttribute(PDO::ERRMODE_EXCEPTION , true);

$xa = new XATransaction(array($db1 , $db2));

// まがいものは登録しない
$xa->begin();
$db1->exec("INSERT INTO xa VALUES('Ialis')");
$db2->exec("INSERT INTO xa VALUES('Sarahsara')");
$xa->rollback();

// こちらのデータは登録
$xa->begin();
$db1->exec("INSERT INTO xa VALUES('Ilias')");
$db2->exec("INSERT INTO xa VALUES('Sara')");
$xa->commit();

// 表示
$s = $db1->prepare("SELECT * FROM xa");
$s->execute();
while($data = $s->fetch()){
	print "$data[0]\n";
}

// Result
Ilias
Sara
 この通り。手間の点から考えても、2つトランザクションを作ってそれぞれコミットするより楽です。ただ、Javaのようなトランザクションマネージャを使わない場合、XAがどれほど堅牢かは知りません。XA PREPAREしてから全部を一気にXA COMMITするのと、全部を普通のトランザクションに入れてから一気にCOMMITするの、安全性がそれほど違うのかどうか。
 ただ、一応これでPHPでもXA Transactionすることができます。

 何にしてもSQLは面白いのです。ATMに使うばかりが脳ではないどころか、そういう使い方しかできないというのは単なる能無しでしょう。
 SQL遊び8回目、番外編のようなものです。今回はSQLの性質について。実際のところ、SQLはCROSS JOINを前提として動作しているものです。ですから、以下の構文はどちらも同じ意味を持っているのですが、
SELECT sample.name , sample_class.class FROM sample 
INNER JOIN sample_class USING(number);

SELECT sample.name , sample_class.class FROM sample , sample_class 
WHERE sample.number = sample_class.number;
 意味としてはどちらも「テーブルをCROSS JOINしてみて、sample.numberとsample_class.numberが一致するものだけを残す」ことになっています。つまり基本はCROSS JOINであり、これまでの問題もそれを利用したものになっていました。
 それでは今回の問題です。

魔道士イリアス「・・・それで、今から舞踏会でもやろうってことになったんだけど。2人セットで飲んで騒いで踊るのね」
騎士サーラ「はい、たまにはそういうのも良いでしょう」
魔道士イリアス「だけどねぇ、参加者はまあ当然内輪なんだけど、せっかくだから男女を組み合わせたい、と考えたのよ」
騎士サーラ「はい」
魔道士イリアス「で、参加者が・・・私、サーラ、シェイン、シェリー、ハードゥン、クラウディ、アマンダなのね」
騎士サーラ「7人ですか」
魔道士イリアス「そう。この7人のうち、男女のペアがいくつできるか、なんだけど・・・。サーラは男女どちらとでもセットにできるようにしようと考えてるわ。そうしたら、一体どれだけの組み合わせが作れるかしら?」
騎士サーラ「なかなか難しい計算ですね・・・」
魔道士イリアス「そういうわけで、いつもながらこのシークェルの魔法でスマートに解決できないかな、と考えてるのよ」
騎士サーラ「スマートとは、答えが一発で出れば良いのですね?」
魔道士イリアス「そういうこと。私とシェリーとアマンダは女性、ハードゥン、クラウディ、シェインが男性、サーラは中性ってことで。みんなサーラの相手を望みそうだけど」

 それでは、この問題をSQLでスマートに解決してください。紙や頭の中にゴタゴタした図を描かずとも、これはSQLでスマートに解消できる問題です。ただし、何十何百、またはそれ以上のレコードの数を(手動もしくはSELECT文の結果報告で)数えたり、人間が足し算の1つもするようなスマートでないやり方ではいけません。

 模範解答に参ります。まず何か適当にテーブルを作ります。ここではテーブルを1つだけ作りましたが、2つまたは人数分作っても構いません。最後はスマートに答えが出ればOKです。1つしか使わない方法の場合には、必ずTEMPORARYでないテーブル(TEMPORARYでは同じSQLに同じテーブルを2つ以上含められないため不可)で作るようにしましょう。
CREATE TABLE pair(name VARCHAR(16) , sex VARCHAR(1)) ENGINE=HEAP;

INSERT INTO pair VALUES('Ilias' , 'w') , ('Sara' , 'n') , ('Shein' , 'm') , 
('Shelley' , 'w') , ('Harden' , 'm') , ('Clowdy' , 'm') , ('Amanda' , 'w');
 性別フラグは何でも構いません。TINYINTでもCHARでも良いですし、SETや文字列を使うこともできます。しかし今回は1文字で。サーラさんはニュートラル。しかし、魔道士イリアスとは相当な身長差があるわけで。マトモに踊れるのでしょうか。
 それでは条件を考えてみます。まず見境なく踊れるとすれば、組み合わせはこうなります。
SELECT COUNT(*) FROM pair AS 'p1' CROSS JOIN pair AS 'p2';

// Result
49
 49人です。数学では「7*7」と教えられますが、その通りです。ただし、重複して踊っている組み合わせがある点、また自分と踊っているパターンがある点を取り除かなければなりません。幸いにもこの問題の解決は簡単であり、自分と踊っているパターンに関しては「p1.name != p2.name」とすれば良いのであり、重複組み合わせに関しては、CROSS JOINの性質からして1組につき2つあるわけですから、単に合計値を2で割るだけです。
 そうすると、とにかく「物理的に踊ることができる」組み合わせは21通りであることが分かります。参加者は「騎士」とか「兵士」とかばかりですから、誰が誰と踊っても一応サマにはなるのですが。
 後は性別によるより分けですが、上のようにしてデータを管理すれば、つまり性別フラグが同じ人とは組み合わせにならない(つまり違う人とは組み合わせになれる)ことになります。相手のフラグが何だろうが、とにかく違いさえすればOKです。騎士サーラと踊りたい人など山ほどいるのですから。
 とすると、次のSQLが導き出されることになります。
SELECT COUNT(*) / 2 FROM pair p1 , pair p2 
WHERE p1.name != p2.name AND p1.sex != p2.sex;

// Result
15.0000
 したがって、15通りの組み合わせがあることが分かります。騎士サーラは全員と踊ることができるため、数式に当てはめて考えようとすれば苦戦することになりますが、SQLならそんなことを考えなくても「性別フラグが違う人とだけ踊れる」と指定すれば値が出てくるのです。
 なに、どういう組み合わせがあるかって?それも問題にしましょう。可能な組み合わせを全部表示してください。ただし、もちろん重複する組み合わせ(「Ilias , Sara」と「Sara , Ilias」など)は排除してください。
 さあ、解けましたか?バカとシークェルは使いよう、大抵のことはできてしまいます。
SELECT DISTINCT ELT((p1.name > p2.name) + 1 , p1.name , p2.name) AS 'First' , 
ELT((p1.name < p2.name) + 1 , p1.name , p2.name) AS 'Second' 
FROM pair p1 , pair p2 
WHERE p1.name != p2.name AND p1.sex != p2.sex ORDER BY First;

// Result
First	Second
Amanda	Harden
Amanda	Shein
Amanda	Clowdy
Amanda	Sara
Clowdy	Ilias
Clowdy	Sara
Clowdy	Shelley
Harden	Ilias
Harden	Sara
Harden	Shelley
Ilias	Sara
Ilias	Shein
Sara	Shein
Sara	Shelley
Shein	Shelley
 ご覧の通り、重複していません。サブクエリやビューを使うには及びません。
 では、例えば男性が必ず1つ目のカラムとか、逆に女性が必ず1つ目とか、中性が必ず最初とか、そういうのはどうやって実装しましょうか。
 こちらもシークェルの魔法にかかれば。
SELECT DISTINCT ELT((p1.sex > p2.sex) + 1 , p1.name , p2.name) AS 'First' , 
ELT((p1.sex < p2.sex) + 1 , p1.name , p2.name) AS 'Second' 
FROM pair p1 , pair p2 
WHERE p1.name != p2.name AND p1.sex != p2.sex ORDER BY First;

// Result
First	Second
Clowdy	Amanda
Clowdy	Ilias
Clowdy	Sara
Clowdy	Shelley
Harden	Amanda
Harden	Ilias
Harden	Sara
Harden	Shelley
Sara	Ilias
Sara	Amanda
Sara	Shelley
Shein	Shelley
Shein	Amanda
Shein	Sara
Shein	Ilias
魔道士イリアス「15組ね。1度に3組ずつ踊れるから、5回交代すれば良さそうね」
騎士サーラ「それ無理です」
魔道士イリアス「あれ?どうして?」
騎士サーラ「上の表、私の取り合いになっていますから。私は最低6回は踊らないと・・・」
魔道士イリアス「全く、組み合わせの計算は面倒ったらありゃしないわよ」

 このように、SQLは舞踏会やオフ会にも欠かせない逸品なのです。

CMT 0 / TB 0
Categories
[開発魔法]
[社会問題]
yamicha.com's Blog