外部イテレータもできた!
前回 のForEachDetailを、外部イテレータからも利用できるためには、外部イテレータが「現在の値」を取得できるようにしないとダメ。2分木のイテレータに関して再開 - チキン煮込みチーズミックス4辛 からは内部イテレータだけに話を絞っていたので、ForEachDetailに渡す処理をActionにしていたけど、もともと 内部イテレータを外部イテレータに変形する(その1:配列の場合)再考 - チキン煮込みチーズミックス4辛 でやってたみたいに、IProcedureにしてやれば外部イテレータ側で「最後に走査された値」を取得できるようになる。
あとは、これらを踏まえて
- IProcessのメソッドProcess
- IProcessWithMemoryのメソッドLastProcessedValue
が確実に交互に呼び出されるようにするといい。
一方で、内部イテレータの方は外部イテレータほど条件がキツくなく、(それが好ましいかはおいといて)IProcedureのProcessが全要素に対して適用されさえすれば問題ない。
ソースコードは以下。
TreeNodeの該当箇所のみ
/// <summary>内部イテレータ</summary> public void ForEach(Action<T> action) { JobHolder holder = new JobHolder(); IProcedure<T> proc = new Procedure<T>(action); ForEachDetail(this, proc, holder, null); while (null != holder.Job) { holder.Job(); } } /// <summary>外部イテレータを取得</summary> public IEnumerator<T> GetEnumerator() { JobHolder holder = new JobHolder(); IProcedureWithMemory<T> proc = new ProcedureWithMemory<T>(); Action init = () => ForEachDetail(this, proc, holder, null); holder.Job = init; return new OutIterator<T>(holder, proc, init); } /// <summary>外部イテレータを取得</summary> System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } /// <summary>委譲される手続きの入口</summary> static void ForEachDetail( TreeNode<T> root, IProcedure<T> proc, JobHolder holder, Action cont) { // rootを根とする部分木の、最小のノードを見つける TreeNode<T> leftmost = root; while (leftmost._left != null) leftmost = leftmost._left; // 見つけたノードから走査開始 // contは、この部分木の走査が終了したあとにすべきこと // 例)この部分木を包むひとつ上の部分木の走査を開始する AtLeftMost(leftmost, proc, holder, cont); } /// <summary>各部分木の最左のノードを処理</summary> static void AtLeftMost( TreeNode<T> leftmost, IProcedure<T> proc, JobHolder holder, Action cont) { proc.Process(leftmost._value); if (leftmost._parent != null) { if (leftmost == leftmost._parent._left) { holder.Job = () => OnTheWay(leftmost._parent, proc, holder); } else { holder.Job = cont; } } else { holder.Job = null; } } /// <summary>部分木のルートに至るまでの途中</summary> static void OnTheWay(TreeNode<T> node, IProcedure<T> proc, JobHolder holder) { proc.Process(node._value); if (node._right != null) { TreeNode<T> nextRoot = GetNextRoot(node); if (nextRoot == null) { // 出口はここしかない? // ForEachでForEachDetailに渡したnullは使われないな... holder.Job = () => ForEachDetail(node._right, proc, holder, null); } else { holder.Job = () => ForEachDetail( node._right, proc, holder, () => OnTheWay(nextRoot, proc, holder)); } } else { if (node._parent != null) { holder.Job = () => OnTheWay(node._parent, proc, holder); } else { holder.Job = null; } } } /// <summary>次の部分木のルートを見つける</summary> static TreeNode<T> GetNextRoot(TreeNode<T> node) { while (node._parent != null) { if (node._parent._left == node) return node._parent; node = node._parent; } return null; }
外部イテレータのクラス。無駄にGenericにしてみた。
class OutIterator<T> : IEnumerator<T> { JobHolder _holder; IProcedureWithMemory<T> _proc; Action _initCont; public OutIterator(JobHolder holder, IProcedureWithMemory<T> proc, Action initCont) { _holder = holder; _proc = proc; _initCont = initCont; Reset(); } public T Current { get { CheckDisposed(); return _proc.LastProcessedValue(); } } public void Dispose() { CheckDisposed(); _holder = null; _initCont = null; _proc = null; } object System.Collections.IEnumerator.Current { get { return Current; } } public bool MoveNext() { CheckDisposed(); if (_holder.Job == null) return false; _holder.Job(); return true; } public void Reset() { CheckDisposed(); _holder.Job = _initCont; } void CheckDisposed() { if (_holder == null || _proc == null) { throw new InvalidOperationException(); } } }
IProcedure、Procedure、IProcedureWithMemory、ProcedureWithMemoryは 内部イテレータを外部イテレータに変形する(その1:配列の場合)再考 - チキン煮込みチーズミックス4辛 を、JobHolderは 2分木のイテレータに関して再開 - チキン煮込みチーズミックス4辛 を参照。もちろんTree
実際に動かすと → http://ideone.com/jrUG1
という事で、一連のイテレータの件は頭の体操になったけど、メリットは...ないな。こんなアホみたいなコード書かれた日にゃぁ、ぶち切れますね。