Monthly Archive for May, 2008

Rubigraph 0.1.0

Rubigraph を 0.1.0 にバージョンアップしました。 gem install rubigraph でアップグレードできます。

変更点

Ubigraph alpha-0.2.2 に対応

Rubigraph 0.0.1 だと、Ubigraph alpha-0.2.2 でうまく動きません。(XML-RPCの返り値が変わったため) バージョンアップ推奨です。


Ubigraphの中の人と、ブログコメントやメールでのやりとりを行い、Ubigraph alpha-0.2.2 から、Rubigraph が含まれることになりました。やった!

証拠魚拓:

公式ドキュメントの中に、ちゃんとクレジットはいってます。


しょぼくてもいいから、とにかくなんかつくって素早く公開する、ということが大事ですね。最初からすごくなくてもいい。公開したあと、徐々に良いものにしていけばいい。

Rubigraph

一行でサマリ

Rubigraph という、Ubigraph のRubyラッパを書きました。

Ubigraphとは

Ubigraph というグラフ生成ツールを最近見つけました。 Graphvizが静的なグラフ生成をしてくれるのに対して、Ubigraphは動的にノードやエッジを追加/削除/属性変更できるのがミソです。これがたのしい。 どれくらい楽しいかについては、デモを見ていただければと思います。

クライアントサーバ型であり、サーバのコードは非公開だけれどXML-RPCのAPIが用意されているので簡単にクライアントがつくれます。なので、クライアント側の言語は問わないと謳ってるわけですね。サーバのバイナリはMacとLinuxが用意されてます。

Rubigraphとは

ですが、いちいちAPIリファレンス見ながらXML-RPCを書いてられない。 なので、簡単なクラスをつくってラップしたものをRubigraphとして公開します。

インストール

Rubyforgeにプロジェクトをつくっておきました。 gem install rubigraph としてインストールできます。

コード

どう書けるようになるのか

require 'rubigraph'
 
Rubigraph.init        # XML-RPCクライアントの初期化などする
 
v1  = Vertex.new
v2  = Vertex.new
e12 = Edge.new(v1, v2)
 
v1.color  = '#003366'
v2.shape  = 'sphere'
e12.label = 'edge between 1 and 2'

かんたんですね!

まとめ

ダイナミックにグラフで可視化するのはたのしいものです。ぼくは関数コールトレーサにつかっていて、とても便利に感じています。 Ubigraph++。

YAPC::ASIA 2008 2日目

昨日に続いて2日目も行ってました。

さいごにLarryがゾンビになって壇上にのぼったときに、Perlが愛されてるのはこの人がつくっているからなのだ、と感じました。あのときの会場の暖かさ。

内容はもちろんのこと、イベント自体のクオリティがとてもすばらしかった。スタッフのみなさまありがとうございました。ぼくもPerlに貢献できるよう、ドメインとってSEOしてPHPアプリをインストールしようと思います。

YAPC::ASIA 2008 1日目

YAPC::ASIA::2008 名札

YAPC::ASIA 2008の1日目に行ってきました。

Perlを書くことはほとんどない (最後に書いたのは10年前) ぼくですが、Perlコミュニティというのはどんなものだろうという好奇心で参加しています。 現実の問題にとりくむプラクティカルなハッカー集団、という印象ですね。

おもしろかったのは Parrot の話でした。用途別にレジスタが4種類あるのですね。ぼくが今つくってるレジスタベースのバーチャルマシンでは、なんでもありオブジェクトが入るレジスタの1種類しかなかったので、参考になります。 ParrotとLLVMを比較してみるのもおもしろそう。

ぼくはというと、話を聴きながらひたすらはてぶCore Dataをつくってました。CodeReposに置いてます。(Changeset 11654あたり)

Picture 1

エクスポートしたはてぶファイルを読み込んで、タグとエントリを一覧表示します。 キーワードによるフィルタリングやタイトルによるソートなどができます。 YAPC中にViewerとしての機能をひととおりつくりあげました。 制約がある方が集中できるものですね。明日は編集の実装に取り組んでみようかな。

懇親会では、くじ引きに当たってDeNAのプレゼントであるゆびクッションをいただきました。ありがとうございます!

DeNAクッション

うちに泥棒が入ってきたときには、これで撃退しようと思います。


明日も参加します。大岡山とおい… ><

黒いMacBookでXcodeを広げてごにょごにょしてる人がいたら、きっとぼくです。声をかけてもらえるととてもうれしいのです (識別しやすいように、名札にいつものアイコンを描いておきました)。

SafariでMigemoを使えるようにしようとした

SIMBLプラグインをつくる習作として、Safariのページ内検索にMigemoをつかえるようにできないか試行錯誤してみました。 分かったことは、SIMBLプラグインとしてつくるのは難しそう、ということでした。

作戦

Migemoの出力は正規表現です。

例: takai → (タカイ|タカイ|隆一|多階層|他界|高[市石鼾泉井]|たかい|takai|takai)

対して、Safariのページ内検索は1つのキーワードに対する全文検索です。 つまり、探すべきキーワードが1つから複数に変わるということです。これは大きな差。

ならば、元のコードでキーワードを渡して検索している関数を、キーワードの個数ぶんだけ繰り返し呼べば所望の機能が実現できそうだ、と考えました。

どこに手を入れるか

Safariの文字列検索を追う で書いていたように、WebView::searchFor にブレークポイントをはり、デバッガでステップ実行して挙動を調べました。

コードを書き変える

まず、元の searachFor メソッドを _safari_searchFor メソッドに変名してとっておき、 この_safari_searchFor メソッドをMigemoの正規表現から得られる文字列の数だけ呼び出す、という戦略を考えます。 うまくいけば、SIMBLプラグインとして実装する際に Method Swizzling として実現できますね。

ためしに、与えられたキーワードと google の2つで検索するようにしてみました。

Index: mac/WebView/WebViewPrivate.h
===================================================================
--- mac/WebView/WebViewPrivate.h        (revision 32849)
+++ mac/WebView/WebViewPrivate.h        (working copy)
@@ -106,6 +106,9 @@
  */
 - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection;
 
+- (BOOL)_safari_searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection;
+
+
 - (void)setMainFrameDocumentReady:(BOOL)mainFrameDocumentReady;
 - (void)setTabKeyCyclesThroughElements:(BOOL)cyclesElements;
Index: mac/WebView/WebView.mm
===================================================================
--- mac/WebView/WebView.mm      (revision 32849)
+++ mac/WebView/WebView.mm      (working copy)
@@ -3002,6 +3002,14 @@
 
 - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection
 {
+  BOOL ret1 = [self _safari_searchFor:@"google" direction:forward caseSensitive:caseFlag wrap:wrapFlag startInSelection:startInSelection];
+  BOOL ret2 = [self _safari_searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag startInSelection:startInSelection];
+
+  return ret1 and ret2;
+}
+
+- (BOOL)_safari_searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection
+{
  if (_private->closed)
    return NO;
 
@@ -3247,24 +3255,27 @@
 
 - (NSUInteger)markAllMatchesForText:(NSString *)string caseSensitive:(BOOL)caseFlag highlight:(BOOL)highlight limit:(NSUInteger)limit
 {
-    WebFrame *frame = [self mainFrame];+    NSString *strs[2] = {@"google", string};
     unsigned matchCount = 0;
-    do {
-        id <WebDocumentView> view = [[frame frameView] documentView];
-        if ([view conformsToProtocol:@protocol(WebMultipleTextMatches)]) {
-            [(NSView <WebMultipleTextMatches>*)view  setMarkedTextMatchesAreHighlighted:highlight];
-            ASSERT(limit == 0 || matchCount < limit);
-            matchCount += [(NSView <WebMultipleTextMatches>*)view markAllMatchesForText:string caseSensitive:caseFlag limit:limit == 0 ? 0 : limit - matchCount];
 
-            // Stop looking if we've reached the limit. A limit of 0 means no limit.
-            if (limit > 0 && matchCount >= limit)
-                break;
-        }
-        
-        frame = incrementFrame(frame, YES, NO);
-    } while (frame);
-
+    for (int i(0); i<2; i++) {
+        WebFrame *frame = [self mainFrame];
+        do {
+            id <WebDocumentView> view = [[frame frameView] documentView];
+            if ([view conformsToProtocol:@protocol(WebMultipleTextMatches)]) {
+                [(NSView <WebMultipleTextMatches>*)view  setMarkedTextMatchesAreHighlighted:highlight];
+                ASSERT(limit == 0 || matchCount < limit);
+                matchCount += [(NSView <WebMultipleTextMatches>*)view markAllMatchesForText:strs[i] caseSensitive:caseFlag limit:limit == 0 ? 0 : limit - matchCount];
+                
+                // Stop looking if we've reached the limit. A limit of 0 means no limit.
+                if (limit > 0 && matchCount >= limit)
+                    break;
+            }
+            
+            frame = incrementFrame(frame, YES, NO);
+        } while (frame);
+    }
     return matchCount;
 }

じっさいに、マッチしたキーワードをハイライト表示させているのは markAllMatchesForText というメソッドのようだったので、 こちらにも複数キーワード対応するためのハックを仕込んでおきます。

実行結果

result

複数キーワードがハイライト表示されました。やりましたね :) ただし、「次の候補へ」のようにして、次のマッチ文字列に飛ぼうとしても、同じキーワード間でしかジャンプできませんでした。 ><

原因と対策

そもそも、キーワードにマッチした元のテキストやマッチした結果はどこに保存されているのか。 これを調べていくと WebCore の中に入っていくことになります。 WebCoreはKHTML由来のコードで、C++で書かれているライブラリです。 となると、Objective-C みたいに実行時に柔軟なメソッド差し替えはできなくなってしまいます。 今回はSIMBLプラグインの習作が目的だったので、深追いはここまで。

本気でやるならば、C/Migemoと鬼車をWebCoreにリンクして、マッチしたテキストの範囲などを複数キーワードに対応させればよいでしょう。

まとめ

あと少しだったんだけどなあ…

手駒

  • Migemoの実装には、C/Migemoをつかいます。
  • Migemoの出力は正規表現なので、鬼車のCocoa版であるOgreKitをつかいます。
  • Safariをハックするには、WebKitをデバッグビルドするのが便利です。
  • Cocoaアプリを拡張するには、SIMBLプラグインをつくるのが定石です。

TopCoder::SRM::401

ひさびさにTopCoderについて書き残してみます。 自分はまだまだへぼいのでDiv2の話。

Easy

問題, コード

二次元座標上の2点が与えられて、その線分上にある格子点 (端点は含まない) の数をかぞえる問題。

2点を通る線分は、

y = (y2-y1)/(x2-x1) * (x-x1) + y1
( x1 < x < x2 )

で表現できるので、(x, y) が格子点かどうかは (y2-y1)*(x-x1) が (x2-x1) で割り切れるかどうかで判断できます。

というコードを10分くらいで書いてsubmitし、200ptsを得たものの何か不安を感じて、

(x1, y1) = (0, 0), (x2, y2) = (0, 50), 期待する返り値: 49

のテストケースをローカルにつくったところ、みごとにアウト。 x2=x1 のケースが境界条件でした。

いそいでこの場合だけを特殊化して再びsubmitし、結果は150pts。ペナルティきつい ><

Medium, Hard

どちらも問題は分かるし、解決策の背中は見えてくるんだけど、コードにできず。これじゃまだまだDiv2から抜けられないな…

Challenge

今回のEasyは、きっと境界条件の処理が甘いコードがあるにちがいないと踏み、さっきやったテストケースでchallengeしてみました。初めてchallengeに成功し、+50pts。

他のひともがんがんchallengeしており、Room内は死屍累々としていました。

result

結果

System Testで無事にEasyがとおり、こんなところ。 Ratingは前回よりも+30で894ptsになりました。あと+6で緑コーダー。

Safariの文字列検索を追う

Safariで、ページ内検索がどのように行われているかを、ちょっと追ってみました。以下かんたんなメモ。

準備

Safari.appはリリースビルドなので、WebKit をつかいます。 Debugging WebKit の手順どおりやると、デバッグシンボルのついたWebKitでSafariがつかえるようになります。

set breakpoint

search というキーワードで、WebKit の中を検索してみたところ、いくつかひっかかります。 WebView:searchFor というのが怪しいんじゃないか、と勘であたりをつけ、ブレークポイントを張り、デバッグ実行 (Cmd-Y)。

実際に、Cmd-F してページ内検索を実行し、ブレークポイントにひっかかったところ: break

まんまとひっかかりましたね。

最終的には、WebCore::CircularSearchBuffer::isMatch という関数内で文字列の比較がmemcmpで行われています。

まとめ

ソースを静的に解析してブレークポイントにあたりをつけ、実行時の挙動を見てハックするポイントをさがす、という例でした。 次は、じっさいにこのあたりに手を入れてみます。

夏ライオンのおれおれビルド

Twitterクライアントの夏ライオンを、きわめて自分好みに改造しました。作者の @akrさん 曰く、どんどん横流ししておk だそうなので、ここで公開しておくことにしました。

ダウンロード

夏ライオンに従って、修正BSDライセンスです。

なにがちがうか

その1: 詰め込み表示

以下の画像の左がオリジナル、右がここで公開してるもの。

tweaked

いっぺんに表示できるメッセージを増やして、より世界の風を感じることができるようになっています。

その2: メッセージフィルタリング

夏ライオンにメッセージフィルタリングをつけるハック で書いたパッチを適用しています。


ほんと、ソースが公開されてるってすばらしいですね! akr++。

つくりたいものがない人のために

プログラムを書きたいけど、何をつくったものか思いつかない症候群というのがあると聞きます (参考:TopCoderが流行ってるみたいなので)。 一方で、つくりたいものがたくさんありすぎて時間が足りない、このままだと死んでも死にきれない、というひともいます。

じゃあ、アイデアが余りあまっているひとはどんどん公開して、コードを書きたいひとはそれをがんがん実装すればみんな幸せになりますね!

よーしアイデア共有サイトみたいなのをつくろうかと考えてたら、CodeReposのticketに登録すればいいじゃないか、と思い当たりました。さっそくideaというticket typeをつくって、どう直す.orgついての案 を登録しておきました。

先のエントリにも書いた (再掲)

  1. いいアイデアを思いつく
  2. コードを書き起こす
  3. どこにバグがあるか見つける
  4. バグに適切な処置をする

の、1と2の間に橋が架かるといいですね。

どう直す.org

どう書く.org は与えられたお題をどのようにプログラムするかにフォーカスしているんだけど、自分で書くことよりも他人のコードを直すことのほうが得意としてるひとも世の中にはたくさんいるはず。

そこで、どう直す.org というデバッグ力を競うサイトをつくることを考えてみます。

  • お題は壊れたコード
    • 一見、どこが間違ってるか分からないほどだとベター
  • 問題がどこにあるかを指摘
    • TopCoder の Challenge Phase みたいなもの
  • 問題に対する解決策となるコードを貼る

アイデアをCodeReposにticketとしてつくっておきました


プログラミングには、

  1. いいアイデアを思いつく
  2. コードを書き起こす
  3. どこにバグがあるか見つける
  4. バグに適切な処置をする

という異なる能力が必要になります。 2についての情報はたくさん溢れていますが、それだけじゃプログラムは完成しない。 1については別に考えるとして、3と4の能力を高めることももっと重視されるべき。 デバッグのテクニックというのはなかなか表に出てこないものなので、こうした暗黙知を共有できるとよいですね。