2009年1月12日月曜日

LINQの独自実装にはIQueryableを実装する

LINQの独自実装をするにはIQeuryableを実装すれば良いのか。色々勘違いしていたぞ。
以下方針のメモ。間違っている部分があるかもしれない。

SkipListNodeSummableField<T>のようなクラスをSkipListNode<TItem, TNode>のインナークラスとして次のように作る。その構造はSkipListNodeに酷似していてやはりツリー類似の構造を持っているのでそのコンテナクラスを作ってやればLINQと整合的に高速な総和計算ができるというのがポイント。ただしこのクラスは自分自身では順序を定める方法を持たない点がスキップリストのノードとは異なる。その代わりにそのインスタンスはSkipListNodeの内部フィールドとして保持される。
class SkipListNodeSummableField<T>{
public TNode ThisNode;
public T[] Values;
public SkipListNodeSummableField<T>[]Next;
public SkipListNodeSummableField<T>[]Prev;

//SkipListNodeのコンストラクタから呼び出してください
public SkipListNodeSummableField(TNode ThisNode){
this.ThisNode=ThisNode;
}

//SkipListNodeのOnHeightDecidedメソッドをオーバーライドして
//このメソッドを呼び出してください
public SetHeight(){
if (values == null) {
Values = new T[ThisNode.Height];
} else {
Array.Resize(ref Values, ThisNode.Height);
}
}
//SkipListNodeのOnMaintainanceメソッドをオーバーライドして
//このメソッドを呼び出してください
public Maintainance(int depth){
if (IsHeader || IsFooter) return;
if(depth==0){
Values[0]=DecideValueDepthZero(ThisNode.Item);
}else{
Values[depth]=SkipList<TItem, TNode>.GetEnumerableInOrder
(ThisNode, true, ThisNode.Next[depth].Prev[depth-1]
.Select(summablefield=>summablefield.Values[depth-1])
.Aggregate(Composite);
}
OnMaintainance(depth);
}

//合成規則
public virtual T Composite(T a, T b){
throw new NotImplementedException();
}
//このメソッドをオーバーライドしてNext[depth]やPrev[depth]に
//ThisNode.Next[depth]やThisNode.Prev[depth]の適当なフィールドを
//指定してください
protected virtual OnMaintainance(int depth){
throw new NotImplementedException();
}
//このメソッドをオーバーライドしてThisNode.ItemからValues[0]を決定する
//方法を指定してください。
protected virtual T DecideValueDepthZero(TItem Item){
throw new NotImplementedException();
}
}


SkipListNode<TItem, TNode>の継承クラスの実装としては例えば
class MyNode:SkipListNode<MyItem, MyNode>{
class VolumeSummableFiled:SkipListNodeSummableField<int>{
public int Composite(int a, int b){
return a+b
}
protected virtual OnMaintainance(int depth){
Next[depth]=ThisNode.Next[depth].Price;
Prev[depth]=ThisNode.Next[depth].Price;
}
protected virtual T DecideValueDepthZero(TItem Item){
return Item.Price;
}
}
public VolumeSummableField Volume;

public MyNode(){
Volume=new VolumeSummableField(this);
}
protected override void OnHeightDecided(){
Volume.SetHeight();
}
protected override void OnMaintainance(int depth){
Volume.Maintainance(depth);
}
}

という感じにする。

その上でコンテナクラスとして、SkipListRangeとSkipListSummableFieldRangeとを作る。これらはSkipListNode型あるいはSkipListSummableFieldRange型のInitial及びFinalフィールドを持つ。そして、SkipListRange型とSkipListSummableFieldRange型に対してIQueryableの実装を行う。これで、対応するノードの集合が連続である事を暗黙に仮定する事で、SkipListRange型に関してはFirst, Last, TakeWhile,
Whereメソッド等の高速化ができる。それが仮定できない場合にはIQueryableにダウンキャストしてやればQueryableクラスが提供する標準の拡張メソッドが使用されるという約束にすれば良い。つまり、インタフェースの明示的な実装という奴を使って、ダウンキャストされた場合全部Queryableクラスの静的メソッドに転送するようにすれば良い。LINQはインタフェースの実装を問わず、対応するインスタンスメソッドが存在すればそれを使い、次いでインタフェースの実装を試し、最後に拡張メソッドを探すので、(この優先順位はどこまでC#と拡張メソッドの仕様でどこまでLINQの仕様なのか良く把握できないないが)これでツリー類似の構造を利用した対応するノードの集合が連続である場合の高速なクエリと、対応するノードの集合が連続でない場合の標準的なクエリとを両立できる。SkipListSummableFieldRangeに関してはSumメソッド(Compositeメソッドによる合成だがLINQの他の実装との整合性からメソッド名はSumにすべきだろう)高速化する事が可能だ。

ここまでやれば、後一踏ん張りするとLINQ句への対応までできてしまうのだが、やっぱり何だか面倒そうだなあ。でも、ノードの集合が連続であると仮定した場合の高速なWhereができると、区間の削除が綺麗に実装できるんだよな。そのうち体調的に余裕がある時にまずLINQの独自実装のサンプルを作る事から初めてみよう。

体調

最近睡眠時間が延長+起きた後夜まで強烈に眠いので今日のトレードは休む事にしていたのだが、16時に起きてみて気が付いた。今日って成人の日か。ラッキー。

そして風邪気味の症状が強まっている。後、昨日の夜顔が火照って37.2度程度の微熱がずっと引かなかった。今までは、微熱は時々奇妙な形で出るのだが、実際に暑さや寒さを感じる事はなく、強い寒さを感じる場合にはむしろ低体温(36度を切る)事が多かった。だから、実際に熱っぽく感じて実際に微熱があるというのは珍しい経験なのだが、これは今日は平熱に戻っていた。

ね、ねむい・・・

強烈に眠い。そして立っているのが辛い程に足に疲労がある。肩と腕に筋肉痛がずっとある。調子に乗って腕立てなんかしなきゃ良かった。咳・痰も少し激しい。でも、寝起きに、あれ、風邪をひいたかな、という感覚を覚えるのだが、風邪っぽい症状については実際には風邪気味とすら言えない程度の症状しかない。慢性化してはいるが。

最近では段々、無理に外に出て夜風に当たっている時しか意識が保てなくなってきた。その癖、夜12時を回ると、グロッキーながら活動的という風になって、まるでゾンビのようにあれこれしながら朝方まで起きている。最近の長文投稿の連続もそんな感じだった。そのせいで首も痛い。手首も痛い。

2009年1月11日日曜日

スキップリストとLINQ

スキップリストを.Net上で実装したライブリをLINQ対応にしたいのだが、中々上手く方針がまとまらない。

AggregateとかSum, Min, Maxとかはノードを引数にとるラムダ式ではなくノードと階層を引数にとるラムダ式を受け取るようにせざるを得ないのでLINQへのつじつまあわせはできない。これは、(ノード,階層)のペアが、区間 [ノード, ノード.Next[depth]) = [ノード, ノード.Next[depth].Prev[0]] に対応するためだ。本当はこれらをEnumerable.Aggregate等と同じように使えるようにしたいのが一番やりたい事だけどそれは無理そう。

でも、IEnumerableの実装(最下層を単純に列挙する)とかICollectionとかインタフェースの実装は最低限やっておくべきだろう。それから、First, Lastを現状のFind, FindLastで
置き換えて高速化はできるだろう。TakeWhileなんかもこれらができれば直ぐだ。

ただし、これらは全部、対応する区間が連続である事が前提なんだよな。そこらへん勝手に仮定して良いのだろうか。

例えばバイナリサーチのようなソートされた構造を前提にした操作とLINQの対応ってどうなっているのか等等、IEnumerableへの拡張メソッドでないコレクションクラス固有のLINQ実装が存在するのか調べておくと後の方針が立て易そうだ。

追記:
そういえば、AggregateメソッドがとるInitial引数は本当は必要ないなあ。初期値としてInitialをセットしてから逐次足しこんでいく実装になっているけれど、初期値を最初の一つにすればInitial引数は必要ない。Enumerable.Aggregate メソッドには三つのオーバーロード(一つ目, 二つ目)があって、今までのスキップリストライブラリのAggregateメソッドの定義はこの一つ目と二つ目の折衷みたいな形になっていたが、Initial引数を失くすことにすれば、一つ目のEnumerable.Aggregateメソッドと見た目が同じになって使う上での混乱もなくなるだろう。

Initial引数は半群における単位元(加法における零)が指定される事を想定しているけれど、正しい数学用語としては、半群は単位元を必ずしも含まず、含む場合はモノイドというらしい。もちろん、半群に一つ要素eを追加して、それと他の要素との群演算をex=xe=xと定める事はいつでもできるから、半群はいつでもモノイドに拡張できる。でもこの事はコードとして実現しようとすると簡単ではない。通常は加法における0とか、重み付き平均における重み0の要素とか、単位元として振舞う要素が初めから含まれているが、そうでないケースもあるだろう。MaxやMinをAggregateを用いて実装する時、InitialとしてDouble.MinやDouble.Maxを指定するのは、正しいけど汚い。真の実数ならばMinやMaxを群演算として半群と見なした場合には単位元は存在しない(-∞や+∞を実数全体の集合に追加してやる事が前述したモノイドへの拡張に対応する)。総和計算を行う可換半群に単位元を持っている事を求めるのは、止められるならば止めた方が良いようだ。

そのうち、Aggregateメソッドのロジックを変更して、Initial引数を取らないようにしよう。

体調

体調が最悪。強烈に眠い。喉と鼻の調子がおかしい。倦怠感。筋肉痛。首にも疲れがある。

ちょっと明日のトレードは休んだ方が良いかも。

2009年1月10日土曜日

AnyWare+とPaypalの比較

Paypalは有名なサービスだけど日本に上陸してまもない。例えば、アメリカの銀行口座からPaypalの口座に振り込む事ができるが、日本の銀行口座から同じ事はできず、日本在住では自分のPaypal口座に入金したり他人のPaypal口座に送金するためにはクレジットカードを登録する事が必須になっている。

一方で、AnyWare+というサービスを知った。これは日本に拠点を置くサービスで、初めからシェアウェア及び寄付を受け付けるフリーウェアを焦点としたサービスだ。一方のPaypalは主にオークションなど個人間の決済を仲介する事を目的としていて、ソフトウェアのドネイションに使用するのは若干本来の趣旨から外れている(ただし海外ではこの利用の仕方はポピュラーではある)。

しかしAnyWare+の手数料を調べて驚いた。20%もマージンを取られる!一方のPaypalは3.4%+0.30USDだ。

例えば先日3000円を受け取ったケースでは、142円の手数料を引かれた。この計算はgoogleで
3000Yen*0.034+0.30USD in Yen
のように検索すれば計算できる。今は円高なので少し少なくなって129円だった。ふむ、為替で意外と変わるな。

また、AnyWare+では銀行口座への入金手数料は実費で発生する。PayPalでは5万円までは250円で固定で場合によっては少し高くつくケースもあるようだが、5万円を超えれば無料だ。

というわけで、手数料20%はどう考えてもぼったくりなので、やはりPaypalで行こう、という結論でした。Paypalの手数料を調べた時はこの手数料を高いと感じたのだが、上には上がいるなあ。

ところで、Paypalは月6万円以上の引き出しのためにはクレジットカードの登録が必要だ。現在の自分の社会的地位が弱いのでクレジットカードって作れなさそうなんだが。当面はPaypal経由の寄付なんてそんなに集まりそうにないのでこの問題は放置。

上でPaypalを寄付に使うのは本来の趣旨から多少外れると書いたが、このページに書いてあるように、寄付は使われ方として想定されているし、寄付用の専用ページとそこへのリンクのために利用するボタンイメージも用意されている。良く見かけるPaypalの寄付ボタンはこれだったのか。しかも、メールアドレスすら公開せずに寄付を受け付けられる(メールアドレスに紐付けられた一意のマーチャントIDが発行される)。そのうち寄付関連のインタフェースを見直す事にしよう。

体調

起きたら午後6時を過ぎていた。やっぱり冬場は睡眠時間が10時間~下手をすると12時間以上に延長するなあ。起きても眠くて眼を開けていられない。足に力が入らないし腕と肩に強い筋肉痛。咳・痰が激しい。ただ、高校~大学院にかけて急速に悪化していった短期記憶の障害が最近改善しているので、こうやってブログへの投稿とかちょっとした作業が辛くないのは嬉しい。

特に高校の頃から、何をするにも、歩いたり風呂に入ったり布団に入りながら寝られずにいたりしている時に、これをするにはまずこれをやって、この事については~に書いてあるはずで、これはこうやればできるはずで、これだけはちょっと不明瞭だから今のうちに調べてまとめておいて、と予行練習と準備を繰り返し繰り返し行う過程を数日~数週間と先行して行ってきた。その上で偶に訪れる(主に夜~朝方)少し意識がはっきりした時間帯にやるべき事をがーっと最初から最後まで推し進める。そうすると、まるで初めからやるべき手順を完全に把握していたかのようにスルスルとタスクが実行できたり、まるで初めから知っていた事をただまとめるだけみたいにノートが作れたり、まるで昔から持っていた見解をそのまままとめているみたいにブログに長文の投稿ができたり、という具合だった。

そういう風に短期記憶が宛てにならないから長期記憶を活かす方向でやっていくしか方法がなかったわけだが、そのお陰で、大学や大学院のセミナーの準備なんかは前日の夜中に突貫工事で行っているのにも関わらず異様にはかどって、学習した覚えのない事を何故かいつのまにか知って/理解していたり、知らない事でも何を見ればそれが書いてあるかを何故か予め知っていたりして、それらをただまとめているだけ、というような不思議な感覚だった。学習が困難な状況にも関わらず却って、少なくとも学習した事項に関しては比較的深く理解できた。

でも、特に大学の頃ぐらいから、例えばWEBをブラウジングしていて、知らない言葉に出くわして、ちょっとWikipediaで調べてみようと思っても、Wikipediaを開いた時にはもう何を調べようとしたかを忘れている、という事を怖いくらい頻繁に経験するようになった。だから、継続的に何かを学習するという事はとても困難だった。大学時代に主体的にアクティブに活動した記憶はほとんどない。一ヶ月かけてMac OS XをLet's Note上のVMWareで動くようにした事くらいだ。

最近はそういう短期記憶が弱まっているという事はなくなった。その代わり眠気のような(でも布団に入って寝たくなるわけでもパタリと眠り込んでしまうわけでもない)額と目の奥のツーンとした感じが強烈に強まっていて集中力がとても阻害される。何とも言えない辛さだ。