2008年04月09日

二次元配列をもっとビュー 5

最終回の今回は検索機能を実装します。まあありきたりなことをするだけですが。

まず検索に使用する IBindingList のメンバを雛形から、また DataView のメンバもピックアップ。

bool IBindingList.SupportsSearching { get { return false; } }
void IBindingList.AddIndex(PropertyDescriptor property);
void IBindingList.RemoveIndex(PropertyDescriptor property);
int IBindingList.Find(PropertyDescriptor property, object key);
public int Find(object key);
public int Find(object[] key);
public DataRowView[] FindRows(object key);
public DataRowView[] FindRows(object[] key);

取り敢えず SupportsSearching は true 返すだけですね。

bool IBindingList.SupportsSearching { get { return true; } }

AddIndex 及び RemoveIndex ですが、これは TwoDimensionalArrayView では実装しません。一応、NotSupportedException 投げるのだけはやめておきます。実装するとしたら指定されたインデックスについて Dictionary でも用意することになるんでしょうかね。

残りは実際に検索を行うメソッドです。ですが、DataView の Find 及び FindRows メソッドは、Sort に指定している列のみを検索対象とし、Sort が空の場合例外を投げるという制限があります。逆に IBindingList.Find にはそういう制限はありません。DataView を IBindingList にキャストして Find させても問題なく検索されます。もちろん Sort 列の検索は高速化されますが。

Sort 列の検索高速化の恩恵は IBindingList.Find 使用時にも与えられますが、ちょっと試した感じそれだけでは説明付かないほど Sort / 非 Sort で速度に差が出ますね。非 Sort 列の場合に使用されると考えられる線形探索が得意なように最初の行でヒットする条件にしても、文字通り桁違いに Sort 列の方が高速です。というか IBindingList.Find が不自然に遅いこともあります。自前で線形探索した方が数倍速いとかどうなんだろう。

今回はあんまり深く考えず、単純に線形探索することにします。なお Sort 列を二分木探索する場合、IBindingList.Find は同一要素がある場合先頭行を返す、と定義されているため、List<T>BinarySearch だと適用すれば終わりってわけには行かないので注意が必要です。

DataView には一つの行のインデックスを返す Find と 条件にヒットする全ての DataRowView の配列を返す FindRows メソッドが用意されています。ArrayView においても取り敢えずソート列から探すメソッドを用意するとして(現状、複数列ソートをサポートしていないので複数キーを受け取るオーバーロードは用意しません)、ソート列が指定されていない限り検索できないってのも利便性に欠けるので、任意の列について検索できるメソッドも用意しましょう。

public int Find(T value);
public int Find(FindConditions<T> conditions);
public TwoDimensionalArrayRowView<T>[] FindRows(T value);
public TwoDimensionalArrayRowView<T>[] FindRows(FindConditions<T> conditions);

こんな感じでしょうか。FindConditions(複数形)は以下のようなクラスにします。少々大仰な気もしますが、こういうの見るとついついやりたくなっちゃいますよね。

// 名前空間直下
public class FindConditions<T> {
    public FindConditions() {}
    public bool IsMatchAll(TwoDimensionalArrayRowView<T> rowView) {
        foreach (FindCondition<T> condition in this.conditions) {
            if (!condition.IsMatch(rowView[condition.ColumnIndex]))
                return false;
        }
        return true;
    }
    public bool IsMatchAny(TwoDimensionalArrayRowView<T> rowView) {
        foreach (FindCondition<T> condition in this.conditions) {
            if (condition.IsMatch(rowView[condition.ColumnIndex]))
                return true;
        }
        return false;
    }
    private List<FindCondition<T>> conditions = new List<FindCondition<T>>();
    public int ConditionCount { get { return this.conditions.Count; } }
    public void AddCondition(FindCondition<T> condition) {
        this.conditions.Add(condition);
    }
    public void AddEqualityCondition(int columnIndex, T value) {
        this.conditions.Add(new EqualityFindCondition<T>(columnIndex, value));
    }
    public static FindConditions<T> CreateAsEquality(int columnIndex, T value) {
        FindConditions<T> conditions = new FindConditions<T>();
        conditions.AddEqualityCondition(columnIndex, value);
        return conditions;
    }
}
public abstract class FindCondition<T> {
    int columnIndex;
    public int ColumnIndex { get { return this.columnIndex; } }
    public abstract bool IsMatch(T value);
    protected FindCondition(int columnIndex) {
        this.columnIndex = columnIndex;
    }
}
public class EqualityFindCondition<T> : FindCondition<T> {
    private T value;
    public override bool IsMatch(T value) {
        return EqualityComparer<T>.Default.Equals(this.value, value);
    }
    public EqualityFindCondition(int columnIndex, T value) : base(columnIndex) {
        this.value = value;
    }
}

では実装です。といっても List<T>.Find や FindAll、あるいは FindConditions<T>.IsMatch での処理がほとんどで、Find/FindRows メソッドそのものはごく簡単なものですけど。

public int Find(T value) {
    // InvalidOperationException の方が妥当な気がするけど DataView の実装に合わせる
    if (this.sortColumn == null) throw new ArgumentException();
    return this.Find(FindConditions<T>.CreateAsEquality(this.sortColumn.ColumnIndex, value));
}
public int Find(FindConditions<T> conditions) {
    if (conditions == null) throw new ArgumentNullException("conditions");
    if (conditions.ConditionCount == 0) throw new ArgumentException("conditions");
    return this.arrangedViews.FindIndex(conditions.IsMatchAll);
}
public TwoDimensionalArrayRowView<T>[] FindRows(T value) {
    // InvalidOperationException の方が妥当な気がするけど DataView の実装に合わせる
    if (this.sortColumn == null) throw new ArgumentException();
    return this.FindRows(FindConditions<T>.CreateAsEquality(this.sortColumn.ColumnIndex, value));
}
public TwoDimensionalArrayRowView<T>[] FindRows(FindConditions<T> conditions) {
    if (conditions == null) throw new ArgumentNullException("conditions");
    if (conditions.ConditionCount == 0) throw new ArgumentException("conditions");
    return this.arrangedViews.FindAll(conditions.IsMatchAll).ToArray();
}
int IBindingList.Find(PropertyDescriptor property, object key) {
    if (property == null) throw new ArgumentNullException("property");
    TwoDimensionalArrayColumnDescriptor column
        = property as TwoDimensionalArrayColumnDescriptor;
    if (column == null) throw new ArgumentException("property");
    return this.Find(FindConditions<T>.CreateAsEquality(column.ColumnIndex, (T)key));
}

しかし何ですね、わざわざ実装する意味があるのかといった感じですね。

さて、五回にわたってお送りした ArrayView もお仕舞いです。改良の余地は山ほどありますがそれはやりたい人がやってください。

posted by Hongliang at 01:25| Comment(0) | TrackBack(0) | C# | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック

ここ(hongliang.seesaa.net)で公開しているものについて、利用は自由に行って頂いて構いません。改変、再頒布もお好きになさって下さい。利用に対しこちらが何かを要求することはありません。

ただし、公開するものを使用、または参考したことによって何らかの損害等が生じた場合でも、私はいかなる責任も負いません。

あ、こんなのに使ったってコメントを頂ければ嬉しいです。

×

この広告は1年以上新しい記事の投稿がないブログに表示されております。