2007-05-31

JavaScriptで出現回数のカウントをする際のコード

今までこう書いてたのが
count[key] = count[key] ? count[key] + 1 : 1;


こう書けることが判明した。
count[key] = ++count[key] || 1;


コメント欄への回答
count[key]がNumberまたはundefinedであるとして、個人的には
count[key] = (count[key] || 0) + 1;
のほうが好みです。
JavaScriptでは問題ないとはいえ、言語によってはi = ++iの動作が未定義だったりするので。

前者の書き方とこの書き方は実は問題があって、Object.prototypeに定義されてるメソッドとハッシュのキー名がかぶるとおかしくなる。なので本当はcount.hasOwnProperty(key)でチェックするのが正しいのだけれど、出現回数のカウントごときでメソッド呼び出しとかしたくない。

その点、後者の書き方だと++count["toString"]が上手いことNaNになってくれるので、気にしなくて良い。ハッシュのキーにprefixつけてる前提ならどちらにしろ気にしないでいいけど。

2007-05-30

HTMLドキュメントを解析して特徴的なループを見つけるBookmarklet

- 全てのDOMノードを列挙する
- ノードは次のように文字列化される。

0: /html[0]/body[0]/div
1: /html[0]/body[0]/div[0]/div
2: /html[0]/body[0]/div[0]/div[0]/ul[0]/li
3: /html[0]/body[0]/div[0]/div[0]/ul[0]/li
4: /html[0]/body[0]/div[0]/div[0]/ul[0]/li
5: /html[0]/body[0]/div[0]/div[0]/ul[0]/li


直前の階層までは添え字つき、最後のノードはタグ名のみにする。
class名、id名は排除する。各々のサイトのルールで記述されたruleよりも
タグのネスト構造の方が変化に強いし機械的に抽出しやすいのではないか?

出現回数でソートする。li要素2-5はループであることが分かる。
しかし、例えばリストの中に複数のリンクが含まれていた場合は、

/html[0]/body[0]/div[0]/div[0]/ul[0]/li[3]/a[0]
/html[0]/body[0]/div[0]/div[0]/ul[0]/li[3]/a[1]
/html[0]/body[0]/div[0]/div[0]/ul[0]/li[3]/a[2]
/html[0]/body[0]/div[0]/div[0]/ul[0]/li[3]/a[3]


というようなループも見つけることができるだろう。
この場合は「より短いパスでのループ」の方がスコアが高くなるようにする。

ループと思われるノード
この場合は/html[0]/body[0]/div[0]/div[0]/ul[0]/liの中身を解析する
例えばこんな感じに。
[
    {level:6, length:(下位のtextNodeのlengthの合計), text: textNodeを連結したもの }
    {level:6, length:(下位のtextNodeのlengthの合計), text: textNodeを連結したもの }
    {level:6, length:(下位のtextNodeのlengthの合計), text: textNodeを連結したもの }
]


- そのノードの配下に含まれる文章の長さが大きいほどスコアが加算される。
- link rel="next"から次のページを取得、同様に解析を行ってtextの内容で重複をチェックする
-- 例えば10件中9件が同じであればスコアを0.1倍に減らす。
-- 変化がないループはタグクラウドのような共通のパーツであると考えられる。
-- 次のページを見たときに内容が変化している箇所がメインコンテンツ部分。
-- ループから見て一つ親のノードがページの主要なコンテンツであると考えられる。
- offsetHeight,offsetWidthを使って描画サイズを調べる?ただしブラウザじゃないと無理。

というようなことを昨晩思いついたのでとりあえず、JavaScriptで書いてみた。
コードはこんな感じ、今のところ重み付けはなくて出現回数のカウントだけ。

http://la.ma.la/misc/js/guess_loop.js

ブックマークレットにして試せるようにしてみたのがこちら。

guess loop

書き忘れ、forEachとか使ってるのでFirefoxオンリーです。

2007-05-09

都合の悪いことを書いたら記事を消さずに修正しろ

削除したブログの元記事がlivedoor readerで消えていないのはなぜですか
http://knowledge.livedoor.com/24355


フィードを取得した際に特定の記事が削除されているかどうかを判別することは難しいです。

ブログの最新記事の内、1件が消されてる、みたいなケースに限れば、多分半日もかからないとは思うけど、古い記事の場合は把握のしようがない。あと、If-Modified-Sinceを解釈して前回取得以降の記事だけを送ってくるようなのもあり得るので、そういうのを間違えて消さないように厳格にやろうとすると、難しいことが多い。

なのでfeed aggregatorを始め各方面にキャッシュされた記事を消したいと思った場合は、単純に記事を消すのではなく「この記事は削除しました」とかで記事の内容を上書きしたほうが良い。

炎上中のブログをリロードしまくってるときにNot Foundならブラウザのキャッシュから復元できることもありますが、
別の内容に置き換わってたりすると「アッー!」ってなったり、よくありますよね。そんな感じ。

やる気がないとかそういうわけじゃないです。

フィードの配信側のコントロールがどこまで及ぶべきかという話は抜きにして(個人的にはそんなにcontrollableでなくて良いと思ってる)、現状「削除しましたよ」ということを相手に通知するのは、消すよりも上書きした方が効果が高いので、そうした方が良いですよ、という話。

2007-05-05

Gearman::UtilとGearman::WorkerをRubyに移植

追記:
元のソースを愚直に書き写しただけなのでRubyっぽくないです。
ちゃんとしたのがciされたようなので用済みです!

http://code.sixapart.com/svn/gearman/trunk/api/ruby/lib/
http://dan-erat.livejournal.com/83941.html

----
http://la.ma.la/misc/ruby/gearman/util.rb
http://la.ma.la/misc/ruby/gearman/worker.rb

RubyでGearmanのWorkerを書いて。
require 'gearman/worker'

worker = Gearman::Worker.new
worker.debug = 1
worker.job_servers("127.0.0.1")
worker.register_function("sum"){|job|
    (a, b) = job.arg.split(",")
    a + b
}
worker.work


Perlの側でこんな風に呼び出せる。
use strict;
use Gearman::Client;

my $gc = Gearman::Client->new;
$gc->job_servers("127.0.0.1");
my $ts = $gc->new_task_set;

for my $i(1..20){
    $ts->add_task("sum" => "3,4", {
        on_complete => sub{
            print "$i:". ${$_[0]}. "\n"
        }
    });
}
$ts->wait;


3+4が34って言われます。
とりあえず動いてるんだけど、なんかタスク追加したあとにselect待ちの状態になってくれないでビジーループになっちゃう。

あとで直す。もしくは誰か直して。