txqz blog

3924件中1から15件目を表示します。このリソース群のタイトルリスト、またリソースのAtom表現RSS1.0表現も参照できます。

普通にphp-mysqliを使ってCRUDするコード例

普通にMysqliを使ってみます。特別なネタがあるわけではありません。リファレンスが分かりにくいという声を近くで聞いたので、まあだいたいこんな風にすれば良いんじゃない? というのをメモとして残しておきます。

個人ユースのデータベースといえばやはり住所録ということで、これにデータを出し入れする部分だけMysqliを使って書いてみます。住所録のデータスキームはこんな感じ:

create table addresses(
    id int unsigned not null primary key auto_increment,
    name varchar(32) not null,
    kana varchar(64) not null,
    zipcode char(8) not null,
    address1 varchar(64) not null,
    address2 varchar(64) not null,
    tel char(13) not null,
    email varchar(255) not null,
    index(kana)
);

IDを指定してデータを取得する関数は、だいたいこんな感じに書ける (実際はもっとまじめにエラー処理とかすべき):

// インスタンス変数として$con = new Mysqli("ホスト名", "ユーザ名", "パスワード", "データベース名");が存在することを前提としている。以下同様
function getAddressById($id) {
    $result = array();
    $sql = "select name, kana, zipcode, address1, address2, tel, email from addresses where id = ?";
    $stmt = $this->con->prepare($sql);
    $stmt->bind_param("i", $id);
    $stmt->execute();
    $stmt->bind_result($name, $kana, $zipcode, $address1, $address2, $tel, $email);
    if($stmt->fetch()) {
        $result = compact("id", "name", "kana", "zipcode", "address1", "address2", "tel", "email");
    }
    $stmt->close();
    return $result;
}

1ページに20件表示させるとして、その20件分を持ってくる関数はこんな感じに書ける:

// $page: ページ数。最初のページは1
function getAddresses($page, $limit = 20) {
    $result = array();
    $sql = "select id, name, kana, zipcode, address1, address2, tel, email from addresses order by kana limit ?, ?";
    $offset = ($page - 1) * $limit;
    $stmt = $this->con->prepare($sql);
    $stmt->bind_param("ii", $offset, $limit);
    $stmt->execute();
    $stmt->bind_result($id, $name, $kana, $zipcode, $address1, $address2, $tel, $email);
    while($stmt->fetch()) {
        $result[] = compact("id", "name", "kana", "zipcode", "address1", "address2", "tel", "email");
    }
    $stmt->close();
    return $result;
}

指定したカナに前方一致するものを取得したいならこんな感じ:

function getAddressesByKana($kana, $page, $limit = 20) {
    $result = array();
    $sql = "select id, name, kana, zipcode, address1, address2, tel, email from addresses where kana like ? order by kana limit ?, ?";
    $offset = ($page - 1) * $limit;
    $like = "{$kana}%";
    $stmt = $this->con->prepare($sql);
    $stmt->bind_param("sii", $like, $offset, $limit);
    $stmt->execute();
    $stmt->bind_result($id, $name, $kana, $zipcode, $address1, $address2, $tel, $email);
    while($stmt->fetch()) {
        $result[] = compact("id", "name", "kana", "zipcode", "address1", "address2", "tel", "email");
    }
    $stmt->close();
    return $result;
}

新しいエントリを挿入するのはこんな感じ:

// $addressはnameやkanaなどのキーがある連想配列
function insert($address) {
    $result = 0;
    $sql = "insert into addresses(name, kana, zipcode, address1, address2, tel, email) values(?,?,?,?,?,?,?)";
    $stmt = $this->con->prepare($sql);
    $stmt->bind_param("sssssss", $name, $kana, $zipcode, $address1, $address2, $tel, $email);
    extract($address, EXTR_OVERWRITE);
    $stmt->execute();
    $result = $this->con->insert_id;
    $stmt->close();
    return $result;
}

まとめて挿入するなら:

// $addressesはnameやkanaなどのキーがある連想配列の配列
function insertAll($addresses) {
    $result = array();
    $sql = "insert into addresses(name, kana, zipcode, address1, address2, tel, email) values(?,?,?,?,?,?,?)";
    $stmt = $this->con->prepare($sql);
    $stmt->bind_param("sssssss", $name, $kana, $zipcode, $address1, $address2, $tel, $email);
    foreach($addresses as $address) {
        extract($address, EXTR_OVERWRITE);
        $stmt->execute();
        $result[] = $this->con->insert_id;
    }
    $stmt->close();
    return $result;
}

更新するなら:

function update($address) {
    $sql = "update addresses set name = ?, kana = ?, zipcode = ?, address1 = ?, address2 = ?, tel = ?, email = ? where id = ?";
    $stmt = $this->con->prepare($sql);
    $stmt->bind_param("sssssssi", $name, $kana, $zipcode, $address1, $address2, $tel, $email, $id);
    extract($address, EXTR_OVERWRITE);
    $stmt->execute();
    $stmt->close();
}

ついでに削除:

function delete($id) {
    $sql = "delete from addresses where id = ?";
    $stmt = $this->con->prepare($sql);
    $stmt->bind_param("i", $id);
    $stmt->execute();
    $stmt->close();
}

function deleteAll($id_list) {
    $sql = "delete from addresses where id = ?";
    $stmt = $this->con->prepare($sql);
    $stmt->bind_param("i", $id);
    foreach($id_list as $id) {
        $stmt->execute();
    }
    $stmt->close();
}

実際はフレームワークとかでささっとやってしまう部分かもしれませんが、あえてフルスクラッチするならこんな感じでしょうか。

タグ

フォーカスを当てるとデフォルト文字列が消えるinput text (jQuery編)

いろんな文献を気軽に参照しにくい環境になり、いちいち検索しなくてもいいようにここにまとめておきたい感じになったのでしばらくそんな普段書かないようなPOSTばかりします。

まず、最近良くある、デフォルトの状態では入力例を表示して、フォーカスを合わせると消えるというインプットボックスの実装例について。このブログのコメント入力フォームがまさにそんな感じ。

HTML:
<form action="hoge.php" method="get">
<p><input type="text" name="q" id="q" size="30" value="検索語を入力してください"><input type="submit" value="検索"></p>
</form>
JavaScript(jQuery):
$(function(){
    $("#q").focus(function() {
        if($(this).val() == $(this).attr('defaultValue'))
            $(this).val('');
    }).blur(function() {
        if(jQuery.trim($(this).val()) == "") {
            $(this).val($(this).attr('defaultValue'));
        }
    });
});

$(this) を3度実行するのは勿体無いので最初に var $this = $(this) して $this を使い回すとか。/ もしくは if ( this.value == this.defaultValue ) this.value = '' でもよさそう。

確かに、このthisはわざわざjQueryオブジェクトにしなくても、そのままDOMのプロパティを参照すれば用が足りますね。このネタそのものが、単体だとわざわざjQueryを使うまでもないレベルですけど。

実例:

フォーカスがあたったときにvalue属性の値がdefaultvalueだったら消して、逆にフォーカスが外れたときに値が空文字列だったらdefaultvalueを表示するというだけ。このdefaultvalueという属性、どうやら仕様書のいうinitial valueを取得するためにW3C DOMのHTMLInputElementで定義されているもので、ちゃんとW3C DOMを実装しているブラウザならちゃんと使えそう。

デフォルト文字を薄くして実際に入力させるときは黒にしたいというときは、あらかじめCSSで#q{color: #999;}などとした上で、

$(function(){
    $("#q").focus(function() {
        if($(this).val() == $(this).attr('defaultValue'))
            $(this).css('color', '#000').val('');
    }).blur(function() {
        if(jQuery.trim($(this).val()) == "") {
            $(this).css('color', '#999').val($(this).attr('defaultValue'));
        }
    });
});

みたいにしたらいいと思う。

あと、Eventを指定するときに、clickとかkeypressみたいなデバイスに依存するものはなるべく避けてfocusやblurを使うべきだってばっちゃが言ってた。

タグ

txqzのURI設計

とくにRFC3986とかで明示されているわけではないけど、Apacheとかを使っていると、http://example.com/hoge/のようなスラッシュで終わるURIはexample.comホストの中にあるhogeフォルダを指していると解釈するのが自然だ。もしDirectoryIndexで指定されたファイルが存在せずIndexesオプションが有効になっていれば、hogeフォルダ以下にあるファイルの一覧が表示される。hogeがフォルダである場合にhttp://example.com/hogeとスラッシュを省略すると、http://example.com/hoge/にリダイレクトされる。

現在のtxqzはブログの記事をデータベースから動的生成して一定期間キャッシュするような設計になっていて、たとえばこのエントリのURIはhttp://txqz.net/blog/2009/05/12/2331だけど、実際にドキュメントルートにblogというフォルダがあってその中に2009というフォルダがあって05というフォルダがあって12というフォルダがあって2331というファイルが置いてあるというわけではない。それでも、なるべく実際にそういう構造になっているかのように見せかけている。たとえば:

http://txqz.net/blog/2009/05/12/2331にアクセス
2009年5月12日23時31分の記事(これ)が表示される
http://txqz.net/blog/2009/05/12/にアクセス
2009年5月12日の記事インデックスが表示される(/2009/05/12/フォルダへのアクセスと捉え、Index of的な表示を行う)
http://txqz.net/blog/2009/05/12にアクセス
2009年5月12日の記事が新しい順に表示される
http://txqz.net/blog/2009/05/にアクセス
2009年5月の記事インデックスが表示される
http://txqz.net/blog/2009/05にアクセス
2009年5月の記事が新しい順に表示される
http://txqz.net/blog/2009/にアクセス
2009年の記事インデックスが表示される (実際は、年単位だと記事数が多くなるので月別の代表的な記事を何件か表示する。特にその月に多くタグ付けされたタグがついている記事を代表的な記事としている。)
http://txqz.net/blog/2009にアクセス
2009年の記事が新しい順に表示される
http://txqz.net/blog/にアクセス
記事インデックスが表示される(全記事の一覧を挙げても多すぎるので、年別の代表的な記事を何件か表示する。選び方は月別と同様。)
http://txqz.net/blogにアクセス
記事が新しい順に表示される

また、古い順に読みたいという需要もあるため、http://txqz.net/blog/2009?1などのように後ろに"?ページ番号"をつけてアクセスするとその範囲の記事が古い順に並べられた部分リストが見られるようになっている。これはhttp://txqz.net/blog/2009/05?1でもhttp://txqz.net/blog/2009/05/12?1でも同様の機能となるが、最近は1日はおろか月に15件も記事を書かないのであまり意味がない。たんに降順だけでなく昇順でも読めますというだけ。ただ、特定の内容について何回かに分けて記事にしてたりすると降順だと読みにくくなったりするので、ぜひ他のブログにも似たような機能をつけて欲しいところ。もちろんMicroformatsには可能な限り対応し、Autopagerizeなどの便利なツールの恩恵に最大限預かれるようにしている。

以上の議論は記事データベースの中からどの範囲の記事リストをどの順で表示するかというものだが、そこから選ばれた記事リストをどういう表現であらわすかという問題もあり、txqzの場合は拡張子をつけることで対処している。http://txqz.net/blog/2009/05/12/2331と拡張子をつけずにアクセスした場合、ユーザーエージェントがtext/htmlをくれと言えばHTML4.01でマークアップしたものをtext/htmlとして返すし、application/xhtml+xmlをくれと言えばXHTML1.1でマークアップしたものをapplication/xhtml+xmlで返す。.htmlという拡張子をつければHTML4.01、.xhtmlならXHTML1.1、.rdfならRSS1.0、.atomならAtom、.jsonならJSONを返す。このブログのフィードがhttp://txqz.net/blog.rdfないしhttp://txqz.net/blog.atomで提供されているのは単にこの仕様に従っただけで、最新の記事というリソースがあって、それをHTMLでマークアップすれば人間向きの表現になり、RDFやAtomでマークアップすれば機械向けの表現になる。リソースとしての意味合いはまったく等しく、単に表現が違うだけなのだから拡張子で違いを表すのが自然だ。

ほかのブログサービスがどうなっているかというと、たとえばはてなダイアリーだと、http://d.hatena.ne.jp/denken/のフィードはhttp://d.hatena.ne.jp/denken/rssで提供されている。完全に後出しじゃんけんなんだけど、ファイル名がrssとあってもなんのRSSか分からないわけで、もし最新の記事のRSSなのだったら、人間向けに/latest.html、機械向けに/latest.rssみたいな感じで提供されていればより分かりやすいURIになりますね。そんなのを追求することがビジネス的にどれだけ意味があるか知りませんが。

ただまあ、こういう内側の仕様や外側のマークアップやUIを考えることばかりに興味がいって、肝心のブログ記事が全然書かれてなかったりするのでナニでアレなわけです。


拡張子で表現内容を変える例としてMitterがある。これは人間向け表現をhttp://mitter.jp/youzaka.html (ただし、拡張子なしでのアクセスが一般的)、機械向け表現をhttp://mitter.jp/youzaka.atomで提供している。もしかしたらapplication/atom+xmlをほしがるユーザーエージェントがアクセスしたら拡張子なしでもAtom版が返ってくるかもしれないけどそこまでは検証してない。

タグ

お釣りの出る商品券を家計簿でどう扱うべきか分からない

クレカのポイントが貯まったのでジェフグルメカード5000円分と交換した。吉野家やモスバーガーなどのファストフード店でも使え、券面500円に満たない買い物にはお釣りが出る。ほかの商品券はクレジットカードが使える店でも有効なものが多く、なるべく現金を使いたくないブームの渦中にいる私としては、最適解はJCBギフトカードや図書カードではなくこのジェフグルメカードだと思う。

で、この商品券、前述のとおりお釣りが出る。吉野家で380円の牛丼を食べることにより、現金120円を得ることができる。この場合、家計簿にはどう付けるべきなのだろうか。商品券をもらった時点で5000円の収入とみなすのか、現金化した時点で収入扱いにした方がいいのか。いちおう現金になった時点で収入扱いだろうということで、家計簿には-120円の支出と書いたけど、ベストプラクティスがあれば知りたい。

タグ

草食系男子というか惣菜を食べる惣食系男子

同じクラスの人たちが男性を中心に6人ほど弁当を持参しており、その流れに沿おうと思って無印でランチボックスを買った。あとお茶はペットボトルに入れて前日凍らせたものを持参するようにしたので、平日にお金を使わないキャンペーンをはじめた。まあキャンペーンといっても単に平日にお金を使わないだけだ。

だいたい朝に作ったものの半分を弁当箱に詰める感じにしていて、件のランチボックスはご飯を入れる容器のほかにおかずを入れる容器が2つ付いているので、シューマイやウィンナー、あと卵焼きにマヨネーズやらニラやらタマネギやらショウガやらを入れてラーユやコショウで味付けしたものなどを入れたりしている。いちどニンニクも下ろして混ぜてみたが、あまりにもにおいがばら撒かれる結果になってしまったのでもうしない。種類が貧弱なのでおいおい増やしていきたい。

あとメディアがいう草食系男子には弁当を持参する者が含まれるそうで、そんな消費を刺激しない事象を取り上げても儲からないだろうに何を狙っているのだろうか。あと、草食と言われますが我々が食べるのは惣菜であってむしろ惣食です。これが言いたかっただけです。

タグ

まとめエントリの画像をOperaで一気にダウンロードする

たとえば「グラデーションが綺麗な画像ください」に挙げられている画像を一気にダウンロードしたいとする。OperaユーザならOperaを使って、しかもなるべく打鍵数を少なくしたい。

  1. F4でパネルを開く
  2. F7でパネルにフォーカスを当てる
  3. Ctrl + Shift + 9でリンクパネルを表示する
  4. クイック検索を使って絞りこんでTabを押してCtrl + aとするか、Shift + などでダウンロードするファイルを指定する
  5. アプリケーションキーなどを押してコンテクストメニューを呼び出す
  6. 「ダウンロードフォルダに保存 (A)」を選ぶ
  7. (゚Д゚)ウマー

フォーカスをあわせるあたりはキーボードショートカットの挙動が微妙だったりするので、適宜ホイールパッドなどを併用するほうがより早くできるかも。

Irvineみたいなダウンロード支援ツールを使うのが王道だろうけど、やりたいことをOpera内で完結させるのがOpera的だと思う。よく分かりませんが。

こんな めんどーな てじゅんで なくても、Ctrl-Jじゃ だめなのかな。

知らなかった。改訂します。

  1. Ctrl + Jでリンクパネルを開く
  2. クイック検索を使って絞りこんでTabを押してCtrl + aとするか、Shift + などでダウンロードするファイルを指定する
  3. アプリケーションキーなどを押してコンテクストメニューを呼び出す
  4. 「ダウンロードフォルダに保存 (A)」を選ぶ
  5. (゚Д゚)ウマー

7手から5手になりました。

タグ

浜松市街地を通り抜けて、ムーンライトながら~の思い出

2009年3月のダイヤ改正の概要が発表された。前々から予告されていたとおり「はやぶさ」と「富士」が廃止になり、「ムーンライトながら」が季節臨化される。研究で遅くなったとき、飲み会で遅くなったとき、徹カラに途中で飽きたとき、浜松駅を3時33分発に発車するこの1番電車には大変お世話になった。大学院の修了とともにこの電車の定期運行が終了するというのも感慨深い。この感慨に浸りすぎていると大学院を修了できなくなって私の人生が終了するかもしれないのでドライにいきたい。


大学を2時半に出て4キロほどの道のりを歩く。昔は姫街道経由で浜松駅を目指していた。途中に読売新聞の販売店があり、いまから1日が始まる配達員の人がこれから1日が終わる私の横でバイクを発車させていたのをよく目にした。真夜中に出歩く不審者と間違えられないように交番の前をやや緊張して通り過ぎて坂を下ると市役所前の交差点に出る。この交差点は横断歩道がなく、その代わりに地下道が設けられている。この時間ともなると地下道では何人かがダンボールなどで陣地取りをしており、私は彼らの睡眠を邪魔しないよう足音を潜ませ、かつ迅速に地下道を通り過ぎる必要があった。この地下道は8方向に階段がある複雑な構造をしたもので、速やかに通り過ぎようと焦って進むと意図しない方向へ出てしまうこともしばしばあった。浜松は二輪メーカーが集積しているが行政は二輪は二輪でも自転車には厳しく、地下道にはスロープがない。自転車はコース取りに気をつけないと同じブロックをただ周回する羽目になる。市役所前交差点で地下道を通るのが煩わしくなったので、最近は姫街道でなく六間道路を経由することにしている。

市役所前交差点から浜松駅への主な道路は、南北に3つ・東西に3つの田の字型に並んでいる。そのうち真ん中の十字部分であるゆりの木通りと有楽街は風俗店や飲み屋の多い歓楽街となっており、この時間に歩くには分が悪い。以前も友人が一人でここら辺を歩いたとき、柄の悪い輩に絡まれタバコを目の前に突きつけられたことがあるという。下辺の鍛冶町通りも似たようなもので、特に外国人がたむろしていることが多い。左辺の連尺通りを進むと、連尺交差点は横断歩道がなく、曲がってゆりの木通りへ入るか地下道へ下る必要がある。ということで、上辺・右辺経由で浜松駅を目指すのが最適解となる。ただ、上辺の道路には通り名がついていないようだ。この通りはマンションや銀行、駐車場や雑居ビルが並んでおり、ひとつ南のゆりの木通りと違って深夜はほとんど誰もおらず静まり返っている。常盤町交差点を右折して右辺の田町中央通りも駐車場や銀行が並ぶ通りで、ゆりの木通り以南が歓楽街の入り口となっていて第一通りとの交差点にややリスクがある以外は問題ない。このコースで早歩きをすると、だいたい駅まで40分で行ける。

深夜の浜松駅は北口の1箇所のみ開いており、入り口に警備員? の人が2~3人いて駅へ入る私へ「おはようございます」と声をかけてくれる。広いコンコースの東端のみが通れるようになっていて、通路はそのまま改札の前まで伸びている。自動券売機と自動改札は出札所に近い2つのみがそれぞれ稼動していて、出札所には駅員が2人ほど詰めていた。少なくとも4人分の深夜の人件費、コンコースやホームでフル出力されている照明、そして自動改札や券売機などの電力などが消費されている中、私は単に定期券を自動改札に通してそのままホームへ上がってしまう。オフシーズンだと浜松からくだりの「ながら」に乗る客など5人といない。寝台特急が何本も停まり、それら単価の高い客の相手をするおまけとして「ながら」の客を待っているのならまだよかったかもしれないが、深夜に浜松駅に停まるくだりの寝台特急は「サンライズ瀬戸・出雲」1つだけ。季節臨化されるのはもっともだし、むしろ2008年度まで残しておいてくれたことがとてもありがたい。「ながら」の使い勝手は浜松駅の発車時刻が20分以上前倒しされ豊橋まで全車指定席になった2007年3月の改正でだいぶ悪くなったけど、それでも卒業のときまで待っていてくれたことがありがたい。

「ながら」は浜松で長時間停車するため、夏冬は保温のために扉は半自動になっている。ボタンを押すとやや大きな音がして扉が開く。オフシーズンはだいたいガラガラだが、それでも週末は混んでいることもあった。一番後ろの座席の右側に座ると、座席と窓の位置の関係で首のすわりがよく、とくに飲み会の帰りなどはよく寝られた。トイレは特急にも使われるだけあって313系のよりゆったりしていて、飲み会の副作用をよく処理していた。豊橋までの間に起こされて指定席券を購入することもあれば、車掌が来ないまま豊橋に着いてしまうこともあった。車掌はたいてい複数人乗務していてそれに運転士も加わるわけで、この電車の運行コストがオフシーズンの乗客数でカバーできるとは思えなかった。

座席ではどうあれ熟睡できないわけで、寝ぼけなまこにずさんな判断でしばしば大変な目にあった。一番ひどかったのが、大高駅だと間違えて飛び降りたら幸田駅だったというものだ。右を見たらホームがあったので乗り過ごしてしまったとろくに確認もせずに降りてしまったのだ。隣の岡崎駅なら30分ほどで始発が出るのだが、幸田では始発は70分後で、ホーム中央にある待合所で震えながら待ち続けた覚えがある。他の失敗はたいてい乗り過ごしで、大高駅で反対電車を16分待ったり、名古屋駅で20分待ったりした。蒲郡で1時間抑止されていることに気づかずひたすら寝てたこともあった。帰宅して大急ぎでシャワーを浴びて朝食を取り、そのまま浜松行きの区間快速で折り返したこともあった。人身事故で遅れた「ながら」をひたすら待ち続けたこともあった。

くだりホームに降りて見上げた5時50分の空は季節によってまったく色が違った。夏の空はすでに明るく、昨日から迷い込んだ私に時が止まっていないことを教えてくれた、冬の空はいまだ夜の続きで、これから寝る私を安心させてくれた。お気に入りは日の出が6時前の時期で、朝から逃げるように原付を西へ走らせることになる。俺の夜はまだ終わっていないんだ!とか一人盛り上がることで家までの気力を持たせることができた。


世間は新幹線0系が引退だとかで騒いでいるけど、私にとっては高校への通学で毎朝乗ったパノラマカーの引退と、大学通学や乗り鉄でお世話になったムーンライトながらの季節臨化のほうが大きい。どちらも特に見送ったりはしないけど、彼らの変わりに新しく鉄路を走ることになる車が、また次世代に思い出を残してくれたらいいなと思う。

タグ

マイクロコンテンツ化が進むよ! やったねたえちゃん!

最寄の腐女子クラスタの皆さんが当研究室B3のガチホモとデジタルネイティブのどちらが左辺でどちらが右辺かというような話で盛り上がっていたので、それに乗っかる形で戯れにBLとか気持ち悪いからたえちゃんとチュッチュッしたいとTwitterにPOSTしたが、文脈を捨ててこのPOST単体だけ読んだらどうもしっくりこないし誤解される。ここでいうたえちゃんとはカワディMAX『少女奴隷スクール』(一水社、2008)に登場する薄倖の少女たえちゃんこと@seiryoであって、性虜ちゃんは女の子だけど男子でラーメンを食べに行ったりする感じでして、これはギャグです。非常に身内向けの。ゆえに文脈なしでTwitterにPOSTしたのは不適切だったと反省しました。Twitterは脱文脈、マイクロコンテンツ化された140字が一人歩きする世界なのですからチャットや日記のつもりで断片を投稿するのは適切な態度ではありません。

それは素敵ですね 情報は受ける側が主体なの知ってますか?

貴方がそう思うなら貴方の中ではそうなんじゃないですか?

た、たえちゃん…!

タグ

Re: ノーベル賞について正しいものを選べ

ノーベル賞について正しいものを選べ。

  1. 物理学、化学、生理学・医学、文学、経済学、平和の6つの部門がノーベル賞として知られるが、平和賞だけはアルフレッド・ノーベル自身の意思に基づいたものではない。
  2. 6つの賞のすべてで日本人受賞者がいる。
  3. ノーベルはスウェーデン人だが、平和賞を選ぶのはノルウェー国会である。
  4. ひとつの部門の賞を日本人が独占したのは、今年の物理学賞が初めてである。

経済学賞を受賞した日本人はいないはずなので2番目の選択肢が正解なのは分かるけど、ひとつの部門の賞を日本人が独占したのは、今年の物理学賞が初めてであるという選択肢が引っかかる。南部さんって日系アメリカ人であって日本国籍は持っていないわけで。日本国籍を持っていなくても親が日本人で日本人っぽい顔をしていたら日本人? ボビーオロゴンみたいなあとから日本国籍をとったみたいな例はこの文脈で日本人として扱われるのだろうか。

経済学賞は1968年にスウェーデン中央銀行の基金に基づき創設された賞で、ノーベルの遺言にはない。経済学賞を受賞した日本人はいない。87年に受賞した利根川進博士はその年の生理学・医学賞の唯一人の受賞者だった。また文学賞は通例、1人に与えられ、川端康成氏、大江健三郎氏はその1人だった。

解説でもその件はスルーされてる。

タグ

電車の中で通話していたら切れられて強制下車させられたのを見た

遅刻していつもより2本遅い電車に乗りかえた。転換クロスシートの4人がけになっているところ(4C)に着席、向かいの窓側(3D)には男性が座っていて、携帯電話でいろんなところに電話をかけていた。なんでも、ストレスで腸が動かないくらいになり退職したので家族やお世話になった人に連絡して回っているらしい。私はかばんからノートパソコンを取り出してPHSにつなげ、低速インターネットを楽しんでいた。

駅間のやや長い区間になったので車掌が車内を巡回し始めた。もっとも車掌室寄りの車両だったので車掌はすぐに私の真横を通り過ぎた。そのまま次の車両へ行くのかと思いきや、向かいで電話をしている人に「車内での通話はご遠慮ください」みたいな注意をした。男性はすぐには通話をやめなかったが、通路を挟んで左奥(5A)に座っていた男性が「そうだやめろ」みたいなことを大声で叫んだので、電話氏はいったん通話を止めた。電話氏が通話をやめても注意氏は引き続き煽っている。電話氏も電話氏で下手に反論を返すので車内に変な空気が流れ出す。電話氏は「じゃあこれはいいんですか」とノートパソコンにPHSをつなげている私を指差してきたが、注意氏はあくまで客室内で通話をしていることについて注意をしているので意に介さない。私は私で話の引き合いに出されても引き続きディスプレイを注視しちょっと口元を緩めながらことの成り行きをTwitterなどで実況し続けており、まったく当事者意識がない。ていうか勝手に当事者にされても困る。

あまりにも注意氏から電話氏への煽りが続くので、電話氏は注意氏の隣(5B)に乗りこみ直接対決を始めた。注意氏は注意氏で「バカ大爆発だな、恥さらしが」みたいなレベルの指摘に終始しているわけで、ストレスで腸の動いていない電話氏には酷だ。そのうち車掌が折り返して戻ってきたので、車掌を含めた3人でつまらない争いが始まり、「降りろ」という注意氏の圧力により電話氏は下車せざるをえなくなってしまった。4A席の人が携帯電話でメールを打っていたのを見て「それやめてもらいたいそうですよ」と言いながら、電話氏は次の駅で下車していった。


注意氏は言葉の上では携帯電話の使用一般について電話氏に注意したようにみえるが、行間を読めばあくまで携帯電話での通話についての注意である。それを拡大解釈して私やメール氏を巻き込むのはよくない。電車内から携帯電話で通話することそのものが社会通念上望ましくないとされているので、注意氏の言葉が足りないことを私やメール氏を巻き込むことで主張しても筋が悪い。いくら、携帯電話から出る電磁波がペースメーカーに悪影響を与えるためにはよほど接近しないといけないんですよみたいな反論をしても分が悪い。車内で喋っていたのは唯一電話氏だけだったのも悪材料だ。

注意氏はイヤフォンを耳につけており、音楽を気持ちよく聴いていたのに電話で退職がどうのという辛気臭い話を聞かされても困るから社会正義を味方に一言言いたかったのかもしれないが、発言があまりにも煽りにすぎるし語彙の方向性が残念すぎる。電話氏が通話を止めて静かになるはずだったのに、注意氏の罵声が取って代わっただけだった。しつこすぎる。電話氏は退職という人生の重大な転換点を迎えて、できるだけ早く関係者と情報共有をしたかったに違いない。そこまで怒られることのようにはみえない。

ただ、ヘタに公共の場で込み入った話をすると、私のような者にコンテンツとして消費されるので、やっぱりやめといたほうがいいかも。

なんだ、この文章は。。一体どちらの側に共感をもってるのかさっぱりわからない。客観描写に終止してるわけでもないし。文章のシュールレアリズム?何か怖い。

ペースメーカー非着用者としてのポジショントークになりますが、私としては常識的な範囲なら車内での携帯通話はかまわないと思います。ただし、もれなく聞き耳を立てますので、不特定多数にいいように利用されるリスクくらいはとってほしいところです。やむをえない事情があって通話をしていたのだと思いますが、電話氏は私やメール氏を巻き添えにしようとした点が気に入りません。注意氏は車内では携帯通話をしてはいけないというルール (車掌も通話を注意したわけで) を全うしようと正義役を買ってでた「いい人」ですが、そのやり方がお粗末過ぎる点が気に入りません。注意氏の罵声と電話氏の身上話では後者のほうがより興味深いので、電話氏の対応がスマートなら電話氏に共感するし、携帯電話が極悪デバイスで周囲に健康被害をまきちらすものなら注意氏に共感します。

電車に駆け込んだのを注意されたので論理的に切れて強制下車させた話が面白かった。

タグ

TKカラオケ

TKオンリーカラオケでH.A.N.Dを3曲歌って帰りたい。"Don't tell yea MAMA"が入っているのはUGAだけなのでそこでよろしく、とかいう

先日書きましたが、先ほどのカラオケが途中からTK縛りになりましたので報告します。機種はUGAでした。

  1. Don't tell yea MAMA (H.A.N.D.)
  2. DEPARTURES (globe)
  3. wanna Be A Dreammaker (globe)
  4. survival dAnce 〜no no cry more〜 (trf)
  5. (tumblin' diceをキャンセルして) Hate tell a lie (華原朋美)
  6. Don't wanna cry (安室奈美恵)
  7. I'm proud (華原朋美)
  8. In the future (hitomi)
  9. Get Wild (TM NETWORK)
  10. (Baby baby baby (dos) をキャンセルして) OVER & OVER (TRUE KiSS DESTiNATiON)
  11. EZ DO DANCE (trf)
  12. a song is born (浜崎あゆみ & KEIKO)
  13. 恋しさと せつなさと 心強さと (篠原涼子 with t.komuro)
  14. CAN YOU CELEBRATE? (安室奈美恵)
  15. FACES PLACES (globe)
  16. YOU ARE THE ONE (TK presents こねっと)
  17. masquerade (trf)
  18. save your dream (華原朋美)
  19. (Brave Storyをキャンセルして) BRAND NEW TOMORROW (trf)

もっと時間があったら「One, Two step」とか「LOOPな気持ち」、あと「Love Train」とかも入れたかったですねえ。

鈴木あみのことがすっぽり抜け落ちていた。Nothing without youとかOUR DAYSとかいいですね。Dancin' in Hip-Hopは勘弁してください。

タグ

はてなブックマークがリニューアルするみたいなので使い方などを振り返る

北九州市立大学のある学生の人はてブ20000件記念を書いていたので私もこれまでのはてブの使い方を振り返ってみたい。

タグつけの推移

その月ごとにどんなタグをつけてきたかをまとめる。初期は中国・韓国・2ちゃんねる関係の割合が異常。2006年2月あたりから「これはひどい」が幅を利かすようになり、それはやがて「あとで」に取って代わる。最近は細かくタギングするように心がけているので、「秋葉原通り魔事件」とか「ゆの in language」のような具体的で懐かしい話題が並ぶようになっている。

2005年2006年2007年2008年
1月
  1. ネタ (70)
  2. ネット (43)
  3. ライブドア (40)
  4. 韓国 (33)
  5. ブログ (33)
  6. まとめ (32)
  7. 萌え (24)
  8. 中国 (23)
  9. 楽天 (22)
  10. はてな (21)
  1. 2ch (39)
  2. 閉鎖騒動 (22)
  3. これはひどい (21)
  4. まとめ (15)
  5. 主張 (13)
  6. ひろゆき (12)
  7. 動画 (9)
  8. これはすごい (9)
  9. 鋭い視点 (9)
  10. あとで (7)
  1. あとで (63)
  2. これはすごい (29)
  3. ニコニコ動画 (25)
  4. 著作権 (25)
  5. Tumblr (16)
  6. Twitter (16)
  7. 就活 (14)
  8. まとめ (14)
  9. 主張 (13)
  10. ネタ (12)
2月
  1. ネタ (98)
  2. ネット (64)
  3. はてな (55)
  4. 萌え (53)
  5. ライブドア (49)
  6. 政治 (44)
  7. これはひどい (43)
  8. まとめ (35)
  9. 民主党 (34)
  10. 写真 (34)
  1. これはすごい (11)
  2. ニコニコ動画 (10)
  3. 動画 (8)
  4. youtube (8)
  5. これはひどい (7)
  6. あとで (7)
  7. はてな (6)
  8. 鋭い視点 (6)
  9. サービス (6)
  10. コメント欄 (5)
  1. あとで (30)
  2. これはすごい (26)
  3. ニコニコ動画 (24)
  4. twitter (14)
  5. これはひどい (12)
  6. ネタ (11)
  7. 著作権 (11)
  8. 就活 (11)
  9. Webサービス (11)
  10. 本 (10)
3月
  1. これはひどい (1)
  2. わらた (1)
  3. 労働 (1)
  4. 労働組合 (1)
  5. 権利 (1)
  6. 社民党 (1)
  7. 言い訳 (1)
  1. ネタ (97)
  2. 写真 (67)
  3. まとめ (64)
  4. セキュリティ (53)
  5. これはひどい (51)
  6. ブログ (43)
  7. はてな (42)
  8. ネット (42)
  9. 松永英明 (41)
  10. 韓国 (37)
  1. これはすごい (19)
  2. これはひどい (17)
  3. 主張 (13)
  4. あとで (11)
  5. 写真 (7)
  6. セキュリティ (6)
  7. まとめ (6)
  8. JavaScript (6)
  9. 萌え (5)
  10. ネット (5)
  1. あとで (44)
  2. ニコニコ動画 (32)
  3. 著作権 (19)
  4. これはすごい (17)
  5. 参考になる (17)
  6. 就活 (13)
  7. 児童ポルノ (12)
  8. ミニブログ (12)
  9. これはひどい (10)
  10. まとめ (10)
4月
  1. ネタ (101)
  2. ネット (93)
  3. 写真 (77)
  4. これはひどい (74)
  5. まとめ (72)
  6. Web2.0 (69)
  7. セキュリティ (67)
  8. Google (66)
  9. ブログ (59)
  10. はてな (52)
  1. あとで (31)
  2. これはすごい (14)
  3. Twitter (12)
  4. Atom (9)
  5. これはひどい (9)
  6. まとめ (9)
  7. Java (9)
  8. API (7)
  9. 主張 (7)
  10. 2ch (6)
  1. これはすごい (23)
  2. まとめ (16)
  3. ニコニコ動画 (16)
  4. 就活 (13)
  5. Twitter (13)
  6. あとで (12)
  7. ネタ (11)
  8. 可視化 (10)
  9. 興味深い (10)
  10. 主張 (10)
5月
  1. 知識 (2)
  2. BitTorrent (1)
  3. P2P (1)
  4. あとでやる (1)
  5. フライパン (1)
  6. 料理 (1)
  7. 生活 (1)
  1. あとで (359)
  2. ネット (125)
  3. まとめ (120)
  4. これはひどい (102)
  5. 写真 (74)
  6. ネタ (72)
  7. ブログ (66)
  8. 動画 (62)
  9. 批判 (54)
  10. Web2.0 (49)
  1. これはすごい (33)
  2. これはひどい (28)
  3. 著作権 (23)
  4. あとで (21)
  5. twitter (17)
  6. 主張 (16)
  7. まとめ (16)
  8. ニコニコ動画 (13)
  9. 写真 (12)
  10. 鋭い視点 (10)
  1. 著作権 (32)
  2. ニコニコ動画 (32)
  3. あとで (28)
  4. まとめ (23)
  5. これはすごい (21)
  6. Twitter (17)
  7. Webサービス (16)
  8. 写真 (13)
  9. Yahoo! (12)
  10. 主張 (12)
6月
  1. 中国 (19)
  2. 韓国 (13)
  3. ブログ (8)
  4. JR (7)
  5. 民主 (7)
  6. 2ch (6)
  7. 北朝鮮 (6)
  8. コスチューム (6)
  9. 自民 (5)
  10. ネット (4)
  1. あとで (170)
  2. まとめ (122)
  3. これはひどい (109)
  4. ネット (109)
  5. ブログ (109)
  6. 写真 (106)
  7. ネタ (84)
  8. Web2.0 (71)
  9. 2ch (67)
  10. Google (61)
  1. _ (85)
  2. あとで (47)
  3. これはひどい (44)
  4. これはすごい (41)
  5. 主張 (34)
  6. ニコニコ動画 (26)
  7. 著作権 (26)
  8. ネタ (19)
  9. まとめ (17)
  10. 声優 (16)
  1. 秋葉原通り魔事件 (40)
  2. ネタ (38)
  3. 主張 (28)
  4. ニコニコ動画 (27)
  5. 著作権 (24)
  6. iPhone (24)
  7. あとで (23)
  8. これはすごい (23)
  9. これはひどい (23)
  10. 毎日新聞 (23)
7月
  1. 韓国 (39)
  2. ネット (32)
  3. 中国 (24)
  4. ブログ (21)
  5. 2ch (17)
  6. 教科書 (14)
  7. ネタ (13)
  8. 日本 (13)
  9. TV (11)
  10. NHK (9)
  1. あとで (173)
  2. ネット (87)
  3. 動画 (87)
  4. Web2.0 (79)
  5. まとめ (79)
  6. これはひどい (66)
  7. ネタ (64)
  8. youtube (62)
  9. 写真 (57)
  10. 著作権 (52)
  1. これはひどい (57)
  2. これはすごい (55)
  3. あとで (47)
  4. ニコニコ動画 (43)
  5. 参院選 (38)
  6. 主張 (27)
  7. らき☆すた (24)
  8. 著作権 (23)
  9. ネタ (23)
  10. まとめ (20)
  1. 毎日新聞 (43)
  2. waiwai (33)
  3. ニコニコ動画 (29)
  4. まとめ (23)
  5. ニコニコ大会議 (20)
  6. 著作権 (17)
  7. iPhone (16)
  8. Twitter (15)
  9. ハゲ問題 (14)
  10. ゆの in language (14)
8月
  1. 韓国 (28)
  2. ネット (18)
  3. ブログ (13)
  4. はてな (11)
  5. 選挙 (10)
  6. 政治 (9)
  7. 中国 (9)
  8. 市民 (8)
  9. ネタ (8)
  10. 2ch (7)
  1. これはひどい (95)
  2. あとで (91)
  3. ネット (85)
  4. 写真 (84)
  5. まとめ (78)
  6. 動画 (64)
  7. Web2.0 (62)
  8. これはすごい (60)
  9. 2ch (53)
  10. 靖国 (52)
  1. これはひどい (36)
  2. これはすごい (31)
  3. あとで (26)
  4. ニコニコ動画 (23)
  5. 主張 (16)
  6. 2ch (14)
  7. らき☆すた (13)
  8. 写真 (12)
  9. アイデア (11)
  10. HTML (11)
  1. 反応 (17)
  2. GSV (14)
  3. あとで (11)
  4. 毎日新聞 (8)
  5. 予告.in (5)
  6. セキュリティ (5)
  7. ネット (5)
  8. テレビ (5)
  9. まとめ (4)
  10. Waiwai (4)
9月
  1. ネット (45)
  2. 選挙 (41)
  3. のまネコ (41)
  4. 中国 (35)
  5. ブログ (29)
  6. 韓国 (22)
  7. 2ch (21)
  8. 著作権 (17)
  9. 民主党 (16)
  10. 言葉 (16)
  1. まとめ (31)
  2. 写真 (29)
  3. あとで (28)
  4. これはすごい (27)
  5. これはひどい (25)
  6. 2ch (20)
  7. Web2.0 (20)
  8. オーマイニュース (17)
  9. ネット (17)
  10. 主張 (16)
  1. ニコニコ動画 (37)
  2. これはすごい (29)
  3. 著作権 (26)
  4. あとで (24)
  5. Mozilla24 (22)
  6. ネタ (19)
  7. これはひどい (18)
  8. アニメ (17)
  9. まとめ (14)
  10. 政治 (14)
  1. あとで (26)
  2. これはすごい (16)
  3. はてな (16)
  4. ネタ (15)
  5. レポ (13)
  6. 考察 (12)
  7. インターン (10)
  8. GSV (10)
  9. Google (9)
  10. 主張 (9)
10月
  1. 2ch (80)
  2. 韓国 (65)
  3. ネタ (64)
  4. ネット (57)
  5. ブログ (46)
  6. 中国 (41)
  7. のまネコ (37)
  8. 靖国 (37)
  9. 萌え (36)
  10. 祭り (34)
  1. これはひどい (26)
  2. ネット (19)
  3. 2ch (18)
  4. これはすごい (18)
  5. あとで (17)
  6. まとめ (15)
  7. Web2.0 (12)
  8. はてなブックマーク (12)
  9. 写真 (10)
  10. 便利 (10)
  1. ニコニコ動画 (59)
  2. 著作権 (43)
  3. これはすごい (33)
  4. 初音ミク (32)
  5. アニメ (29)
  6. これはひどい (23)
  7. ネタ (23)
  8. TBS (20)
  9. あとで (18)
  10. 音楽 (18)
  1. 永上裕之 (30)
  2. あとで (24)
  3. ニコニコ動画 (23)
  4. これはすごい (20)
  5. これはひどい (19)
  6. まとめ (19)
  7. 主張 (17)
  8. webサービス (15)
  9. 本 (14)
  10. javascript (13)
11月
  1. ブログ (82)
  2. ネタ (66)
  3. VIP (60)
  4. 韓国 (56)
  5. ネット (51)
  6. はてなブックマーク (46)
  7. はてな (46)
  8. 2ch (45)
  9. 中国 (40)
  10. Google (40)
  1. これはひどい (15)
  2. これはすごい (14)
  3. あとで (13)
  4. まとめ (9)
  5. 2ch (9)
  6. セキュリティ (8)
  7. 萌え (8)
  8. ひろゆき (7)
  9. まったく新しいタイプ (7)
  10. 動画 (7)
  1. ニコニコ動画 (55)
  2. あとで (38)
  3. これはすごい (28)
  4. ネタ (21)
  5. JavaScript (19)
  6. Twitter (19)
  7. 業界 (18)
  8. IT (18)
  9. ネット (17)
  10. 著作権 (17)
  1. ニコニコ動画 (7)
  2. 小室哲哉 (5)
  3. 音楽 (5)
  4. 写真 (5)
  5. 著作権 (4)
  6. 因子分析 (4)
  7. これはひどい (4)
  8. 政治 (3)
  9. セキュリティ (3)
  10. SPSS (3)
12月
  1. ブログ (103)
  2. ネット (98)
  3. ネタ (93)
  4. 韓国 (75)
  5. VIP (46)
  6. はてな (46)
  7. まとめ (46)
  8. Web2.0 (45)
  9. はてなブックマーク (42)
  10. 知識 (42)
  1. あとで (33)
  2. これはひどい (11)
  3. まとめ (10)
  4. 主張 (9)
  5. 著作権 (9)
  6. これはすごい (9)
  7. Google (8)
  8. JavaScript (7)
  9. 2ch (7)
  10. Winny (7)
  1. ニコニコ動画 (53)
  2. あとで (52)
  3. 著作権 (37)
  4. これはすごい (32)
  5. ネタ (28)
  6. Twitter (25)
  7. まとめ (22)
  8. 初音ミク (19)
  9. 就活 (14)
  10. 考察 (14)

月ごとの特徴的なタグ

TF-IDF的な発想。ほかの月にはあまり出現せず、その月の出現数が多いタグはより特徴的であると考えて、単純にその月の出現数÷出現した月数を計算すると以下のようになった:

特徴的なタグ?
年-月タグスコア
2007-06_42.5000
2008-07ニコニコ大会議20.0000
2008-10永上裕之15.0000
2008-07ゆの in language14.0000
2008-07ハゲ問題14.0000
2007-06アーツ14.0000
2008-06秋葉原通り魔事件13.3333
2006-01後の祭り12.0000
2006-05あとで11.5806
2007-01閉鎖騒動11.0000
2007-10亀田10.0000
2006-08亀田興毅9.6000
2007-07参院選9.5000
2006-04エイプリルフール9.3333
2008-10芋畑行政代執行9.0000
2006-03ワンワンワールド9.0000
2006-05炭谷アナ9.0000
2008-07waiwai8.2500
2005-09のまネコ8.2000
2006-06統一協会8.0000

1位がアンダースコアになってしまった。一時期、タグをつけるのが面倒な記事に一律アンダースコアをタグとして与えていたためにこうなった。あとはだいたいそのときそのときの話題が見えてくる、ような気がする。この類は気の持ちようが大切。

タグの出現頻度

タグのユニーク数は9156で、総出現数は77913。総ブクマ数は23140なので、1記事あたり平均3.38のタグがつき、1つのタグは平均8.51記事に出現している計算になる。しかし実際は典型的なロングテール、冪分布になっている。最も出現頻度の高い「あとで」は1489記事につけられ、以下「ネタ」(1193)、「ネット」(1141)、「まとめ」(1027)、「これはひどい」(1026)と続く。1回しか出現しないタグが4945種類もある。グラフにすると以下のようになる:

タグの出現頻度プロット

ほかのサービスと絡めた利用状況推移

はてブ単体での細かな利用状況推移ははてブチャートなどを見ればわかるのでよしとして、ここではTwitterやTumblrなどをほかのサービスの利用状況とあわせて見てみたい。

サービスごと投稿数推移

はてブをはじめるようになってブログの投稿数が減り、そのはてブの投稿数もTwitterやTumblrの開始後は減少傾向にある。簡単なツールほど利用しやすいのは当たり前で、そりゃそうなるよなあと思った。

タグ

4,5年前のXSLTファイルを上げなおし

1から書き直した」へのコメントで指摘されたので当時のXSLTファイルをアップロードしなおした。手元のマシンにないファイルも多かったが、Wayback Machineから2つほどサルベージできた。当時のファイルは外付けHDDのどれかに眠っているはずなので、折をみてアップロードしなおしたい。ていうか件のエントリとかDFN要素から索引を作るやつとか見直してみると、正規表現でやればいいのに無理してXSLTを使っているあたりが面白い。正規表現も知らずにXSLTばかりやっている

あとコメント一覧を出すページは前々から実装しようとは思っているのだけど、まだその域に達していない。

タグ

ノートパソコン上のOperaでldRのピンを立てすぎるとブラウザが不安定になるのでbを多用するようにした

ほかのマシンで動いているOperaにはこのようなことがないのでマシンとの相性とかマシンそのものの性能によるものだと思うけど、とかくよくOperaが落ちる。最近のメモだけでもこれほどOperaが落ちている。(ITmediaや@ITはhttp://div.itmedia.co.jp/*やhttp://js.revsci.net/*をコンテンツブロックしたら固まらないようになった。)

ピンを立てて大量に開いてからブラウザに固まられると非常に困るわけで、今まで一度に30個開いていたのを10個に制限したが、それでも不安定なときは不安定になる。そこで、LDR Full Feedを使ってなるべくldR上で読み、ブックマークをldR上から直接行うことにした。ただ、bキーで移動できるhttp://b.hatena.ne.jp/add?mode=confirm&url=URLからだとほかの人のコメントとかつけたタグとかが見られないので、それをコメントフォームの下にくっつけるUserJSを書いた。はじめてUserJSを書いたので作法的にまずいところや変なところがあるかもしれない。

hateb_comment_display.js適用後の画面イメージ

ついでにはてなスターも押せるようにしたくて、XMLHttpRequestでコメントのRSSを持ってきて加工してDOMツリーに加えた後にscript要素をcreateElementしてhttp://s.hatena.ne.jp/js/HatenaStar.jsを読み込もうとしたのだけれど、どうもうまくいかなかった。JSAN DEMOで使われているwait関数を使ってHatena.Star.EntryLoaderオブジェクトがロードされるのを待てばいいのかなと思っていろいろやってみたけど、結局HatenaStar.jsそのものをUserJSに入れてしまうのが早いと思って暫定的にそうしてみた。どうせリニューアルまでの短い命になるだろうし。

あと、ReblogCommandのOpera版があるといいなと思って、自分のわかる範囲であれこれしてみたのだけれど、Security error: attempted to read protected variableとかmessage: Security violation - user scripts onlyとか出て無理っぽいので長期ペンディング。「Firefox向けに作成されたGreasemonkeyを動かす」で紹介されていたUserJSをもってしてもできない。ていうか私なんかが簡単な改造でさくっとできてしまうのなら、もうReblogCommandのOpera版が存在していておかしくない。ピンを立てた後にコンテクストメニューの「ファイルを開くアプリケーション」からFirefoxを選んで、FirefoxからReblogCommandを走らせればとりあえず解決。TumblrのFeedは特定の場所にまとめておくようにする。

タグ

Jungで相関行列のグラフ化

ファイルからデータを読み込んでグラフを表示する

JUNGはPageRankや中心性の計算やクラスタリングとかにも使えるJavaのグラフ構造ライブラリなのだが、日本語のまとまった解説文書がTECHSCOREくらいにしか見当たらない。でもTECHSCOREの解説を見れば大体使えたりする。使い方が変更されているメソッドや非推奨になったクラスもたまにあるが、JavaDocを見ればだいたい解決する。

ということで、相関行列を読み込んで、有意な関係をグラフ化するものをJUNGを使って作ってみようと思う。そのために、SourceForgeからJUNGのJARファイルをダウンロードし、依存ライブラリであるCommons CollectionCern Colt(行列計算ライブラリ)をビルドパスに含める。

まず相関行列のデータが必要なので、アイスクリーム統計学のデータから作成した。ExcelファイルをダウンロードしてPEARSON関数を20回コピペするだけの簡単なお仕事。終わったら表頭とデータ部分をコピーしてicecream.txtを作成する。1行目は「バニラ」や「ストロベリー」といった表頭変数がタブ区切りで並び、2行目から相関行列の各行ベクトルがタブ区切りで並ぶことになる。それらを読み込んでVertexを作り、閾値以上のVertexにEdgeを張ってJFrameに表示させる。ソースはこんな感じ:

package net.txqz.neta;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;

import javax.swing.JFrame;

import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.Vertex;
import edu.uci.ics.jung.graph.impl.UndirectedSparseEdge;
import edu.uci.ics.jung.graph.impl.UndirectedSparseGraph;
import edu.uci.ics.jung.graph.impl.UndirectedSparseVertex;
import edu.uci.ics.jung.visualization.FRLayout;
import edu.uci.ics.jung.visualization.Layout;
import edu.uci.ics.jung.visualization.PluggableRenderer;
import edu.uci.ics.jung.visualization.VisualizationViewer;

public class RelationGraph {
  public static void main(String[] args) throws NumberFormatException, IOException {
    JFrame window = new JFrame("RelationGraph");
    final Graph graph = new UndirectedSparseGraph();
    double threshold = 0.2;
    // 表頭変数の配列
    String[] names = null;
    // 相関行列
    double[][] correlation = null;
    LineNumberReader in = new LineNumberReader(
        new InputStreamReader(new FileInputStream("D:\\data\\icecream.txt"), "MS932")
    );
    String line;
    while((line = in.readLine()) != null) {
      // 1行目は表頭変数。ついでに列の数も数えて配列を作成する。
      if(in.getLineNumber() == 1) {
        names = line.split("\t");
        correlation = new double[names.length][names.length];
      }
      // 2行目以降はn行目のデータを配列の[n-2]番目に入れる。
      else {
        String[] t = line.split("\t");
        double[] d = new double[names.length];
        for(int i = in.getLineNumber() - 1; i < t.length; i++) {
          d[i] = Double.parseDouble(t[i]);
        }
        correlation[in.getLineNumber() - 2] = d;
      }
    }
    
    // Vertexの配列を作ってそれぞれに参照を代入
    Vertex[] vertices = new Vertex[names.length];
    for(int i = 0; i < names.length; i++) {
      vertices[i] = graph.addVertex(new UndirectedSparseVertex());
    }
    
    // 閾値以上の相関係数を持っているVertex同士にEdgeを結ぶ
    for(int i = 0; i < correlation.length - 1; i++) {
      for(int j = i + 1; j < correlation[i].length; j++) {
        double current = correlation[i][j];
        if(current >= threshold || current <= -threshold) {
          graph.addEdge(new UndirectedSparseEdge(vertices[i], vertices[j]));
        }
      }
    }
    
    Layout layout = new FRLayout(graph);
    PluggableRenderer renderer = new PluggableRenderer();

    VisualizationViewer viewer = new VisualizationViewer(layout, renderer);
    
    window.add(viewer);
    window.setSize(600, 600);
    window.setLocationRelativeTo(null);
    window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    window.setVisible(true);
  }
}
図1: まず表示させてみた

ノードの名前を表示させる

丸と線しかないので何がなんだかわからない。どのアイスがどの頂点に対応しているのかがわからないと解釈のしようがない。そのためにStringLabellerを使う。あと、頂点と頂点を結ぶ線に相関係数のデータを含ませるためにEdgeWeightLabellerが使える。該当部分を書き直してみると:

    // Vertexの配列を作ってそれぞれに参照を代入
    Vertex[] vertices = new Vertex[names.length];
    final StringLabeller stringLabeller = StringLabeller.getLabeller(graph);
    final EdgeWeightLabeller weightLabeller = EdgeWeightLabeller.getLabeller(graph);
    
    for(int i = 0; i < names.length; i++) {
      vertices[i] = graph.addVertex(new UndirectedSparseVertex());
      try {
        stringLabeller.setLabel(vertices[i], names[i]);
      } catch (UniqueLabelException e1) {
        e1.printStackTrace();
      }
    }
    
    // 閾値以上の相関係数を持っているVertex同士にEdgeを結ぶ
    for(int i = 0; i < correlation.length - 1; i++) {
      for(int j = i + 1; j < correlation[i].length; j++) {
        double current = correlation[i][j];
        if(current >= threshold || current <= -threshold) {
          Edge e = graph.addEdge(new UndirectedSparseEdge(vertices[i], vertices[j]));
          weightLabeller.setNumber(e, current);
        }
      }
    }

そしてPluggableRendererに:

    renderer.setVertexStringer(new VertexStringer() {
      public String getLabel(ArchetypeVertex v) {
        return stringLabeller.getLabel(v);
      }
    });

とすると下図のようにノードに名前が表示されるようになる。グラフがどのように表示されるかは実行のたびに変化するが、トポロジー的には変化していない。TECHSCOREの解説ではUserDataContainerを使っているが、StringLabellerを使うほうが直感的だと思う。執筆時にはなかった機能なのかもしれない。

図2: ノードに名前が表示された

相関がプラスかマイナスかで色を変える

さきほどEdgeWeightLabellerに相関係数を入れたので、これを使って辺に色をつけてみる。こういうのはPluggableRendererをいじると実現できる。

    renderer.setEdgePaintFunction(new EdgePaintFunction() {
      public Paint getDrawPaint(Edge e) {
        return weightLabeller.getNumber(e).doubleValue() > 0
               ? Color.PINK
               : Color.CYAN;
      }
      public Paint getFillPaint(Edge e) {
        return null;
      }
    });
図3: 色をつけた

相関の強さに応じて線の種類を変える

同様に、EdgeWeightLabellerを使って辺の種類も変えてみる。

たとえば、相関係数0.4未満を点線、0.4以上を太線にするなら:

    renderer.setEdgeStrokeFunction(new EdgeStrokeFunction() {
      public Stroke getStroke(Edge e) {
        return Math.abs(weightLabeller.getNumber(e).doubleValue()) < 0.4
               ? PluggableRenderer.DOTTED
               : new BasicStroke(2);
      }
    });
図4: だいぶ見通しがついてきた

選択されたノードと、それに隣接しているノードの色を変える

頂点をクリックすると、選択された頂点と辺で結ばれた隣の頂点の色と選択された頂点から延びる辺の色が変わるようにしたい。

頂点や辺が選択されたことを検知するにはPickedInfoを使う。PluggableRendererはPickedInfoインタフェースを実装しているが、PluggableRenderer#isPicked()はDeprecatedなので、PickedInfoにキャストしてからisPickedメソッドを実行する必要がある。先ほどいじっていたPluggableRenderer#setEdgePaintFunction()とPluggableRenderer#setEdgeStrokeFunction()、あと加えてPluggableRenderer#setVertexPaintFunction()を変更する。非選択ノードをデフォルトの赤から灰色に変えた上で選択されたノードは橙色に、隣接するノードのうち相関係数がプラスなのは赤でマイナスなのは青に、隣接するアークも同様に相関係数によって青か赤に変更し、点線も太くしよう。内部クラスからrendererを呼ぶので、rendererにfinal宣言が必要。

    renderer.setVertexPaintFunction(new VertexPaintFunction() {
      public Paint getDrawPaint(Vertex v) {
        return Color.DARK_GRAY;
      }

      public Paint getFillPaint(Vertex v) {
        if(((PickedInfo)renderer).isPicked(v))
          return Color.ORANGE;
        for(Object n : v.getNeighbors()) {
          if(((PickedInfo)renderer).isPicked((Vertex)n))
            return weightLabeller.getNumber(v.findEdge((Vertex)n)).doubleValue() < 0
                   ? Color.BLUE
                   : Color.RED;
        }
        return Color.LIGHT_GRAY;
      }
    });
    renderer.setEdgePaintFunction(new EdgePaintFunction() {
      public Paint getDrawPaint(Edge e) {
        for(Object v : e.getIncidentVertices()) {
          if(((PickedInfo)renderer).isPicked((Vertex)v)) {
            return weightLabeller.getNumber(e).doubleValue() < 0
                   ? Color.BLUE
                   : Color.RED;
          }
        }
        return weightLabeller.getNumber(e).doubleValue() > 0
               ? Color.PINK
               : Color.CYAN;
      }
      public Paint getFillPaint(Edge e) {
        return null;
      }
    });
    renderer.setEdgeStrokeFunction(new EdgeStrokeFunction() {
      public Stroke getStroke(Edge e) {
        for(Object v : e.getIncidentVertices()) {
          if(((PickedInfo)renderer).isPicked((Vertex)v)) {
            return Math.abs(weightLabeller.getNumber(e).doubleValue()) < 0.4
                   ? new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1.0f, new float[]{1.0f, 5.0f}, 0f)
                   : new BasicStroke(2);
          }
        }
        return Math.abs(weightLabeller.getNumber(e).doubleValue()) < 0.4
               ? PluggableRenderer.DOTTED
               : new BasicStroke(2);
      }
    });
図5: 選択されたノードとその隣接ノードの表現を変えた

PluggableGraphMouseで操作性を向上する

ノードをクリックして自分自身と隣接ノードの色を変えられたのはいいが、左ボタンから指を離すと色が元に戻ってしまう。それが不便なときはPickingGraphMousePluginPluggableGraphMouseに加えるとよい。

    PluggableGraphMouse gm = new PluggableGraphMouse();
    gm.add(new PickingGraphMousePlugin());
    
    VisualizationViewer viewer = new VisualizationViewer(layout, renderer);
    viewer.setGraphMouse(gm);

その他、グラフを回転させたいならRotatingGraphMousePlugin (Shiftを押しながらドラッグするとグラフを回転できる)を、PDFビューワのようにドラッグでグラフ表示領域の移動がしたいならTranslatingGraphMousePluginを、ホイール回転で拡大縮小させたいならScalingGraphMousePluginをそれぞれPluggableGraphMouseにaddしていくとよい。こういうビジュアルな効果はこだわりだすと果てしないけどやってて面白い。拡大したときとかにスクロールバーがほしければGraphZoomScrollPaneをJFrameオブジェクトにaddすれば表示されるようになる。

図6: ズームとスクロールバー

最終的にソースは以下のようになった:

package net.txqz.neta;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Paint;
import java.awt.Stroke;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;

import javax.swing.JFrame;

import edu.uci.ics.jung.graph.ArchetypeVertex;
import edu.uci.ics.jung.graph.Edge;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.Vertex;
import edu.uci.ics.jung.graph.decorators.EdgePaintFunction;
import edu.uci.ics.jung.graph.decorators.EdgeStrokeFunction;
import edu.uci.ics.jung.graph.decorators.EdgeWeightLabeller;
import edu.uci.ics.jung.graph.decorators.StringLabeller;
import edu.uci.ics.jung.graph.decorators.VertexPaintFunction;
import edu.uci.ics.jung.graph.decorators.VertexStringer;
import edu.uci.ics.jung.graph.decorators.StringLabeller.UniqueLabelException;
import edu.uci.ics.jung.graph.impl.UndirectedSparseEdge;
import edu.uci.ics.jung.graph.impl.UndirectedSparseGraph;
import edu.uci.ics.jung.graph.impl.UndirectedSparseVertex;
import edu.uci.ics.jung.visualization.FRLayout;
import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
import edu.uci.ics.jung.visualization.Layout;
import edu.uci.ics.jung.visualization.PickedInfo;
import edu.uci.ics.jung.visualization.PluggableRenderer;
import edu.uci.ics.jung.visualization.ShapePickSupport;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.LayoutScalingControl;
import edu.uci.ics.jung.visualization.control.PickingGraphMousePlugin;
import edu.uci.ics.jung.visualization.control.PluggableGraphMouse;
import edu.uci.ics.jung.visualization.control.RotatingGraphMousePlugin;
import edu.uci.ics.jung.visualization.control.ScalingGraphMousePlugin;
import edu.uci.ics.jung.visualization.control.TranslatingGraphMousePlugin;

public class RelationGraph {
  public static void main(String[] args) throws NumberFormatException, IOException {
    JFrame window = new JFrame("RelationGraph");
    final Graph graph = new UndirectedSparseGraph();
    double threshold = 0.2;
    // 表頭変数の配列
    String[] names = null;
    // 
    double[][] correlation = null;
    LineNumberReader in = new LineNumberReader(
        new InputStreamReader(new FileInputStream("D:\\data\\icecream.txt"), "MS932")
    );
    String line;
    while((line = in.readLine()) != null) {
      // 1行目は表頭変数。ついでに列の数も数えて配列を作成する。
      if(in.getLineNumber() == 1) {
        names = line.split("\t");
        correlation = new double[names.length][names.length];
      }
      // 2行目以降はn行目のデータを配列の[n-2]番目に入れる
      else {
        String[] t = line.split("\t");
        double[] d = new double[names.length];
        for(int i = in.getLineNumber() - 1; i < t.length; i++) {
          d[i] = Double.parseDouble(t[i]);
        }
        correlation[in.getLineNumber() - 2] = d;
      }
    }
    
    // Vertexの配列を作ってそれぞれに参照を代入
    Vertex[] vertices = new Vertex[names.length];
    final StringLabeller stringLabeller = StringLabeller.getLabeller(graph);
    final EdgeWeightLabeller weightLabeller = EdgeWeightLabeller.getLabeller(graph);
    
    for(int i = 0; i < names.length; i++) {
      vertices[i] = graph.addVertex(new UndirectedSparseVertex());
      try {
        stringLabeller.setLabel(vertices[i], names[i]);
      } catch (UniqueLabelException e1) {
        e1.printStackTrace();
      }
    }
    
    // 閾値以上の相関係数を持っているVertex同士にEdgeを結ぶ
    for(int i = 0; i < correlation.length - 1; i++) {
      for(int j = i + 1; j < correlation[i].length; j++) {
        double current = correlation[i][j];
        if(current >= threshold || current <= -threshold) {
          Edge e = graph.addEdge(new UndirectedSparseEdge(vertices[i], vertices[j]));
          weightLabeller.setNumber(e, current);
        }
      }
    }
    
    Layout layout = new FRLayout(graph);
    final PluggableRenderer renderer = new PluggableRenderer();

    renderer.setVertexStringer(new VertexStringer() {
      public String getLabel(ArchetypeVertex v) {
        return stringLabeller.getLabel(v);
      }
    });

    renderer.setVertexPaintFunction(new VertexPaintFunction() {
      public Paint getDrawPaint(Vertex v) {
        return Color.DARK_GRAY;
      }
      
      public Paint getFillPaint(Vertex v) {
        if(((PickedInfo)renderer).isPicked(v))
          return Color.ORANGE;
        for(Object n : v.getNeighbors()) {
          if(((PickedInfo)renderer).isPicked((Vertex)n))
            return weightLabeller.getNumber(v.findEdge((Vertex)n)).floatValue() < 0
                   ? Color.BLUE
                   : Color.RED;
        }
        return Color.LIGHT_GRAY;
      }
    });
    renderer.setEdgePaintFunction(new EdgePaintFunction() {
      public Paint getDrawPaint(Edge e) {
        for(Object v : e.getIncidentVertices()) {
          if(((PickedInfo)renderer).isPicked((Vertex)v)) {
            return weightLabeller.getNumber(e).floatValue() < 0
                   ? Color.BLUE
                   : Color.RED;
          }
        }
        return weightLabeller.getNumber(e).doubleValue() > 0
               ? Color.PINK
               : Color.CYAN;
      }
      public Paint getFillPaint(Edge e) {
        return null;
      }
    });
    renderer.setEdgeStrokeFunction(new EdgeStrokeFunction() {
      public Stroke getStroke(Edge e) {
        for(Object v : e.getIncidentVertices()) {
          if(((PickedInfo)renderer).isPicked((Vertex)v)) {
            return Math.abs(weightLabeller.getNumber(e).floatValue()) < 0.4
                   ? new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1.0f, new float[]{1.0f, 5.0f}, 0f)
                   : new BasicStroke(2);
          }
        }
        return Math.abs(weightLabeller.getNumber(e).floatValue()) < 0.4
               ? PluggableRenderer.DOTTED
               : new BasicStroke(2);
      }
    });

    PluggableGraphMouse gm = new PluggableGraphMouse();
    gm.add(new PickingGraphMousePlugin());
    gm.add(new RotatingGraphMousePlugin());
    gm.add(new TranslatingGraphMousePlugin());
    gm.add(new ScalingGraphMousePlugin(new LayoutScalingControl(), 0));
    
    VisualizationViewer viewer = new VisualizationViewer(layout, renderer);
    viewer.setGraphMouse(gm);
    viewer.setPickSupport(new ShapePickSupport(viewer, viewer, renderer, 2));
    
    window.add(viewer);
    window.setSize(600, 600);
    window.setLocationRelativeTo(null);
    window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    window.add(new GraphZoomScrollPane(viewer));
    window.setVisible(true);
  }
}

マジックナンバーが多いとか、そもそもこんなの全部mainメソッドの中でやっていいのかとか、まああるけど簡単なサンプルということで。あと、このサーバでJavaが動けばこの下にAppletとか置けてよりそれっぽい紹介ができるのだけど、動かないので残念。まあ、ヘタにAppletなんか置くとページの読み込みが激重になるだろうし、いいか。

タグ
© 2001-2009 Chisa YOUZAKA. Some rights reserved.