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時を回ると、グロッキーながら活動的という風になって、まるでゾンビのようにあれこれしながら朝方まで起きている。最近の長文投稿の連続もそんな感じだった。そのせいで首も痛い。手首も痛い。