俺カスタムなイテレータ (2)
2008/08/31 19:02 - デザインパターン
俺カスタムなイレテータ作成について続けます。
インターフェイス
インターフェイスのコードは基本そのまま。 何のヒネリもなし。
だから軽~く流します。
イテレータのインターフェイス IIterator.as
package { public interface IIterator{ function hasNext():Boolean; function next():Object; } }
ものによっては Iterator のインターフェイスには上記ふたつのメソッドに加え、reset メソッドが定義されている場合があります。 私も reset はインターフェイスに記述しておくべきメソッドだと考えますが、後ほど AqIterator.as の説明の際に述べる理由から今回は定義しません。
コレクションのインターフェイス ICollection.as
package { public interface ICollection{ function iterator():AqIterator; } }
これはもうそのままですね。 iterator メソッドしか定義しません。
クラスの設計
今回実装したいのは、コレクションが保持する配列(以下「対象配列」)へ正順、逆順、ランダムでのアクセスです。 アプローチとしては幾つか手段が考えられます。
たとえば、next メソッドでは配列[idx++] によって正順アクセス、逆順アクセスは prev メソッドというのを作って配列[idx--] をおこなうなんてことが考えられます。
でもこの流れだとランダムアクセスができなさそう。
では、対象配列それ自体をかき混ぜてしまう手段はどうか。
逆順アクセスの場合は対象配列を reverse。 ランダムアクセスの場合は shuffle というカスタムメソッドで対象配列をシャッフル。
そして配列へのアクセスは常に next メソッドを使って配列[idx++] でおこなう。
これもダメ。 一度シャッフルが入るとそれ以降、正順も逆順も二度と取得できなくなります。
てなワケで私は以下の手法を採用しました。
対象配列と同じ length の配列(以下「アクセス配列」)を別途用意します。 アクセス配列の要素は 0 から始まる正の整数で増分は 1 とします。
以下のような感じ。
対象配列 target = [ Shape0, Shape1, Shape2, Shape3, Shape4 ]; アクセス配列 access = [0, 1, 2, 3, 4];
アクセス配列の各要素は対象配列のインデックスとして使用します。
手順としては以下のとおり。
- アクセス配列に reverse や shuffle を適用
- アクセス配列に先頭からアクセスし、要素を取得
- アクセス配列から取得した要素を対象配列のインデックスとして使用
- 対象配列の当該位置にある要素を取得
- 2.~4.の繰り返し
逆順を模式化すると以下のようになります。
アクセス配列を reverse access = [4, 3, 2, 1, 0]; ↓ アクセス配列を先頭から読み込む。 要素を対象配列のインデックスとする ↓ 1番目に取り出される対象配列の要素 target[ access[0] ] = target[4] = Shape4 2番目に取り出される対象配列の要素 target[ access[1] ] = target[3] = Shape3 3番目に取り出される対象配列の要素 target[ access[2] ] = target[2] = Shape2 4番目に取り出される対象配列の要素 target[ access[3] ] = target[1] = Shape1 5番目に取り出される対象配列の要素 target[ access[4] ] = target[0] = Shape0
シャッフルについても同様。
で、二度目以降のイテレートは、アクセス配列を昇順ソートして初期状態に復旧させ、そのまま使うなり、reverse するなり、shuffle するなりすれば良いわけです。
このように配列を二段階使うことで、対象配列への正順、逆順、ランダムでのアクセスを、モードを切り替え何度でもおこなうことが可能になります。
続く。
シリーズ