2006 年 5 月 15 日 19 時 1 分

PuzzleLine: 行・列クラス


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


本来の解法アルゴリズムを考えるには、まだまだ準備がいる。
しばらくは設計が続きそうだ。

PuzzleSolver はおいといて、PuzzleLine を考えよう。

PuzzleLine は、単独の行や列に限定したクラスである。
ののぐらむの基本は、1 つの行に着目して解いていくので、
解答を進める上で、行という単位で扱えると便利だ。

では、PuzzleLine に必要なメソッドを洗い出す。
・内部コンストラクタ
・行の範囲内で解答を求める: Solve()

Private フィールド。
・解答情報: m_oAnswer
・ヒント配列: m_oHints

意外と少ない。親から利用することを考えると、
以下のような情報を持たせてもいいかも。

プロパティ。
・行や列のインデックス取得: Index
・行か列か取得: IsColumn
・解答状況取得: Progress
・ヒント取得: Hints

見ての通り、殆どがプロパティである。
PuzzleLine インスタンスは、
コンストラクタによって初期化され、
基本的に情報の参照のみの機能となる。

唯一の処理メソッドである Solve は、
解答のアルゴリズムに関わる大事なメソッドだ。

こんな感じだろうか。
PuzzleSolver は 行や列毎に PuzzleLine を作成し、
行や列の情報の管理を PuzzleLine に委託する。

では、PuzzleLine のコンストラクタを考える。
PuzzleLine が自らの役割を果たすために必要な情報は、
コンストラクタで渡してやる必要がある。

・解答情報
・ヒント配列
・行か列か
・自分の行・列内のインデックス位置

これをもとにシグネチャを作ってみるとこうなる。

    Friend Sub Construct( _
            ByVal Answer As PuzzleAnswer, _
            ByVal Hints As ArrayList, _
            ByVal IsColumn As Boolean, _
            ByVal Index As Long)

PuzzleAnswer はまだ作っていないが、
解答情報を保有させるクラスである。
Hints は、IPuzzleLoader のプロパティを使えるように、
ArrayList で受け取ることにしよう。
IsColumn は、列なら true だ。
3 次元とかは考えてないのでこれでよし。
Index は、行や列の位置となる。

コンストラクタは、後で利用できるように
これらの情報をフィールドに格納しておく。

よし、一気に作ってみよう。

    Private m_oAnswer    As PuzzleAnswer
    Private m_oHints     As PuzzleHintCollection
    Private m_lProgress  As Long
    Private m_fColumn    As Boolean
    Private m_lLineIndex As Long
    Private m_lCellCount As Long ' 行・列の長さ

    Friend Sub Construct(_
            ByVal Answer As PuzzleAnswer, _
            ByVal Hints As ArrayList, _
            ByVal IsColumn As Boolean, _
            ByVal Index As Long)

        Dim i As Long
   
        ' メンバの初期化
        m_lProgress = 0
        m_fColumn = IsColumn
        m_lLineIndex = Index
        If IsColumn Then
            m_lCellCount = Answer.Height
        Else
            m_lCellCount = Answer.Width
        End If
        Set m_oAnswer = Answer
   
        ' ヒントコレクションを初期化
        Set m_oHints = New PuzzleHintCollection
        Call m_oHints.Construct(Hints)

    End Sub

    ' 行・列のヒントを取得する
    Public Property Get Hints() As PuzzleHintCollection
        Set Hints = m_oHints
    End Property

    ' 行・列の位置を取得する
    Public Property Get Index() As Long
        Index = m_lLineIndex
    End Property

    ' 行か列かどうかを取得する
    Public Property Get IsColumn() As Boolean
        IsColumn = m_fColumn
    End Property

    ' 行・列の状態を取得する(用途は確定していない)
    Friend Property Get Progress() As Long
        Progress = m_lProgress
    End Property

Solve 以外の実装はこんな感じになるだろうか。

PuzzleSolver から要求があれば返せるように、
プロパティも幾つか定義しておく。
ここで、ヒント配列を返却する際に、
以前と同じような問題が浮上する。

ArrayList は読み書き自由であるため、
格納している ArrayList をそのまま返却すると、
呼び出し側で自由に書き換えられてしまうと困る。

ヒント配列を自由に書き換えられるようにしてもいいのだが、
それを許すと、アルゴリズムの最適化ができなくなるので、
今回はそれを許可したくない。

そのためには、Hints プロパティによって、
ArrayList の「コピー」を返却するという手があるが、
何度も Hints にアクセスした場合、非常に効率が悪い。

まあ、PuzzleLine から見れば、PuzzleSolver も身内なので、
ArrayList をそのまま返して、
変更しないでねって紳士協定の手もあるのだが、
親しき仲にも礼儀ありってのが俺の持論だ(笑)

ではどうするか。

C++ の時代では、const 参照という手法があったが、
VBA にはそんなものはない。
代替策は、「不変クラスを作成する」である。

C# や Java の String クラスも不変クラスである。
一度インスタンスを構築すると、
インスタンスの中身を変更することはできない。
こういうクラスなら、インスタンス参照を受け渡すときに、
コピーや変更を気にせずに渡せるようになる利点がある。

もちろん、複雑な不変クラスのインスタンスを作成する場合、
コンストラクタだけでは力不足だが、
そういうクラスのインスタンスを作るためには、
ビルダ(Builder)という別のクラスを使う。
C# や Java では、これがはやりのようだ。

さて、今回に当てはめてみると、
PuzzleHintCollection というクラスである。
これは、ヒント配列をラップした不変クラスと考えよう。

これを使えば、Property Get Hints で
直接フィールドを返却することができるので効率が良い。

クラスの設計で注意する必要があるのは、
メソッドや値を設定する事が可能なプロパティ、
そして、不変以外のオブジェクトを返すプロパティだ。
これらは注意しておかないと、後々大きな問題を生みやすい。

PuzzleLine はがちがちの設計としたので
Solve メソッド以外で値の変更はありえない。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.