Hatena::ブログ(Diary)

@jitteの日記 RSSフィード

2006-06-09

[] Gmailアカウント作成

仕事でお世話になっている某氏に無理矢理お願いして招待してもらった。変なSNS機能とかついてたらやだなあ。なさそうだな。

2006-05-20

2006-05-16

[] 最速検索の勉強 - CPAN最速検索

こんにちは、最速検索co(さいそくけんさくこ)です。嘘ですごめんなさい。

今回はCPAN最速検索を読んでみよう、という無謀な試み。

モジュールのリストを改行で区切ったテキストファイルを受信してクライアントサイドで検索させています。クライアントサイドで検索させているのでサーバー負荷は全くかかりません。

最速インターフェース研究会 :: CPAN最速検索

えー、前回ソースをだらだらと載せてしまったのですが今回は関数名とポイントだけに絞ります。必要なら各自ソースを並べて読んでください。


bodyタグ

  • bodyタグの中はmainとmenuのdivタグ、menuの中はdistとdocのdivタグという構造。distに何が表示されるかわかりませんがとりあえず読み進めます。
  • あ、menuの下にresultのdivタグがありました。ここはインクリメンタル検索結果っぽい。inputタグでonkeydown="keydown.call(this,event)"となっていますので、keydown関数で更新ですね。
  • ie_xmlhttp.jsを読み込んでいますが後回し。

gid()

  • gid()の気持ちはよくわかります。うわ、$に代入して$(id)とかできるんですね。
  • 初期化関数はinitと。

function addSidebar(title, url)

  • 省略

function loadperldoc(pn)

  • pnは選択中のパッケージ名が入るようです。
  • あ、perldocの内容は都度サーバに問い合わせるんですね。

function pn2dist(pn)

  • // パッケージネームからdistを探す
  • A::B::C → A::B → A のように探しているようです。複数のパッケージをまとめて「配布」しているのがdistということのようです。あ、そういえばPerlモジュールはpackage 〜;とか書きましたね。

function searchdist(pn)

  • setTimeout(f,600)しています。600msecですよね。0.6秒後にfを呼んでいる。多分keydownあたりから呼ばれていると想像。
  • f()ではdistのinnerHTMLを更新しています。ああ、配布名とパッケージ名のリンクを作っていました。リンク先は両方ともloadperldoc(this.innerHTML)。

function init()

  • 初期化関数ですね。ajax()を2回呼んでいます。それぞれコールバックを登録しているようですが、いったん後回しにします。
function init(){
  ajax("./list", function(data){
    find = new Finder({
      src    : data,
      formatter : formatter,
      view   : printto("result"),
      onchange : searchdist
    });
    watch("query", find.do_search);
    $("query").focus();
  });
  ajax("./dist", function(data){
    dist = data;
  });
}

function formatter(text,sel)

  • textをspanで囲ってonclick="loadperldoc(this.innerHTML)属性をつけます。
  • さらにselが真ならclass="selected"属性もつけます。

function printto(id)

  • id要素に値を設定する関数を返します。

function keydown(event)

  • 「上下で選択、エンターでperldoc表示」のようです。

function watch(id, callback, msec)

  • id要素が変更されたかどうかをmsec(省略すると200)間隔で確認し、変更されていたらcallbackを呼び出します。

function Finder(option)

  • お、1画面で収まらない。いったん読み飛ばします。

Array.prototype.map = function(callback,thisObj)

  • RubyでいうArray#mapのようです。

String.prototype.find = function(q,op)

  • RubyでいうString#matchのようです。
  • opはop.max||10という使われ方で件数を絞るのに使っているようです。

function ajax(url,callback)

function ajax(url,callback){
  $("status").innerHTML = "now loading...";
  var req = new XMLHttpRequest;
  req.open("GET",url,true);
  req.onload = function(){
    var data = req.responseText;
    callback(data);
    $("status").innerHTML = "loaded."; 
  };
  req.send(null)
}
  • ははあ、第1引数URLでしたか。本当にコールバックを登録してGETするだけのようです。

function init() [再掲]

function init(){
  ajax("./list", function(data){
    find = new Finder({
      src    : data,
      formatter : formatter,
      view   : printto("result"),
      onchange : searchdist
    });
    watch("query", find.do_search);
    $("query").focus();
  });
  ajax("./dist", function(data){
    dist = data;
  });
}
  • ajax()の定義を見つつ読み下してみます。
  • おお。http://cpan.ma.la/listhttp://cpan.ma.la/distに検索結果を先に入れてある。
  • ./listの方は、入力エリアを0.2秒間隔で監視して変更があればFinder()にパラメータを渡して作った関数で再検索。ははあ、onkeydownでは再検索しないと。
  • ./distの方は、バックグラウンドで内容を変数distに取り込んでいました。

[] CPAN最速検索が最速な理由

  • 更新頻度の低いデータをテキスト形式で事前に準備してある(基本)
  • そのデータはバックグラウンドで転送する(Ajax
  • 配列にせずテキストのまま検索している(実装テク)
  • Perldoc自体は都度取得しているので余計なメモリ消費オーバヘッドや事前処理がない(実装テク)
  • クロージャの徹底活用(Ajax

ふむふむ。記述順序と実行順序がここまでバラバラなのはスゴイなあ。いったん誰かがやったのを模倣するのは辛うじてできそうだけど。

あ、今回XML使ってないからAjaxとは言わなかったりするのかな。まあいいや。今日はここまで。

2006-05-08

[] 最速検索の勉強

○○最速検索ってのを作ってみたいです。とりあえず有名なawsearch.js - Amazon最速検索ライブラリの勉強開始。

うーん、JavaScriptとXSLTですか。両方苦手なのでちょっと手間取りそう。


awsearch.js

うろ覚えのJavaScript知識で読み進めてみます。

* 利用例
var dev_token = "あなたのdeveloper token";
var xslt = "http://example.com/awsearch.xsl";
var aws = new AWSearch(dev_token, xslt, {
  mode: 'music-jp',
  onSuccess: function(result){
    alert(result.items[0].title + ' - ' + result.items[0].creator.join(','));
  }
});
aws.search('Beatles');
aws.search('Billy Joel');

AWSearchがクラスと。パラメータをつけてインスタンスを生成しawsに代入と。aws.searchで検索実行と。AWSearchのコンストラクタの第三引数は連想配列かな(文法忘れた、確かkey: valueのペアを並べて書けた)。modeは「和書」とかカテゴリを指定するっぽい。onSuccessで検索成功したときのコールバックを指定すると。コールバックの引数としてresultに連想配列が渡されてくると。

ふむふむ。

var AWSearch = function(dev_token, xslt, options){
  return new AWSearch.Search(dev_token, xslt, options);
};

AWSearch.results = {};
AWSearch.callbacks = {};

おや。AWSearchはクロージャですかね。さっきnew AWSearchとかしてたのはちょっと不思議ですが読み進めましょう。内部ではAWSearch.Searchをnewして返していますね。検索処理に共通の機能やデータをAWSearchに持たせて、個別の検索はAWSearch.Searchに持たせているっぽいです。とするとresultsは直前の検索結果でしょうか。callbacksはコールバックそのものっぽいですね。

次はユーティリティクラスっぽいですね。

AWSearch.Utils = {
  extend: function(destination, source) {
    for (property in source) {
      destination[property] = source[property];
    }
    return destination;
  },

  bind: function(method, object) {
    return function() {
      return method.apply(object, arguments);
    }
  }
};

extendはプロパティを上書きコピー。bindはメソッドをオブジェクトに追加しているようです(Rubyでいう特異メソッドかな)。

AWSearch.Searchの定義はこんな感じ。

AWSearch.Search = function(){ this.initialize.apply(this, arguments); };
AWSearch.Search.prototype = {
  ...
};

1行目はコンストラクタinitializeにパラメータを渡しているようです。JavaScriptだとこうなるんですね。2行目以降はAWSearch.Searchクラスのメソッド定義みたいです。

実際の定義内容。

  initialize: function(dev_token, xslt, options){
    if(!dev_token)
      throw('dev_token is required.');
    if(!xslt)
      throw("xslt's uri is required.");
    this.dev_token = dev_token;
    this.xslt = xslt;
    this.setOptions(options);
  },

コンストラクタの定義です。パラメータをローカル変数に代入しているようです。

  setOptions: function(options) {
    this.options = {
      asid: 'nomusinolife-22',
      searchType: 'KeywordSearch',
      mode: 'books-jp',
      type: 'lite',
      page: 1,
      locale: 'jp'
    };
    AWSearch.Utils.extend(this.options, options || {});
  },

オプションのデフォルト値を定義し、指定されたオプションで上書きしています。

  search: function(keyword){
    if(!keyword)
      return false;
    var script = document.createElement('script');
    script.charset = 'UTF-8';
    var name = '';
    with(this.options){
      script.src = 'http://xml-jp.amznxslt.com/onca/xml3?t=' + asid + '&dev-t=' + this.dev_token + '&' + searchType + '=' + encodeURI(keyword) + '&mode=' + mode + '&type=' + type + '&page=' + page + '&f=' + this.xslt + '&locale=' + locale;
      name = mode + '_' + keyword + '_' + page;
    }
    AWSearch.callbacks[name] = AWSearch.Utils.bind(function(result){
      this.count = result.count;
      if(this.options.onSuccess)
        AWSearch.Utils.bind(this.options.onSuccess, this)(result);
    }, this);

    if(typeof this.options.onCreate == 'function')
      this.options.onCreate();
    document.body.appendChild(script);
  }

scriptタグを作る。そのソースをアマゾン検索APIとする。パラメータは、オプションを適切につなげる。ここで、xslを使う。コールバックをセットする。onCreateが指定されていたらこのタイミングで呼び出す。scriptタグをdocument.bodyに追加する。

コールバックのところは難しいですね。

    AWSearch.callbacks[name] = AWSearch.Utils.bind(function(result){
      this.count = result.count;
      if(this.options.onSuccess)
        AWSearch.Utils.bind(this.options.onSuccess, this)(result);
    }, this);

bindを使って、現在のコンテキストを第1引数クロージャに割り当てます。クロージャ内のthisも現在のコンテキストっぽいですね。onSuccessが定義されていたらこれも現在のコンテキストで呼び出すようです。

[] Amazon最速検索が最速な理由は

想像ですが、

  • Amazonの検索APIが速い(サーバが高性能?)
  • XSLTの変換が速い(ローカルで最適化バイナリを実行?)
  • 両者を直結している

っていうことでしょうかね。

実はもっと単純に検索結果をAJAX+ローカルキャッシュとかで瞬時に出す仕組みを期待していたので、それは別途調べないといけないことがわかった、というオチがつきました。