VBAでスタック クラスを作る

ラボ

文字列型のスタッククラス

 スタック構造とは、値を積み上げていき(PUSH)、上から順に取り出す(POP)ような形式を言います。


 今回は使いやすいようにクラスで実装してみたいと思います。

 動的配列の扱いやスタック構造を解りやすくするため文字列型にしています。整数型にしたい場合は適宜書き換えて下さい。
 Variant型の場合は少々工夫が必要になると思います。

 VBAの動的配列は、追加していく時には、ReDim Preserve とすれば簡単にできますが、配列自体を縮小していくという事はできません。
 そのため、一旦他の配列に移して、削除(Erase)して再度作り直す必要があります。
 これですと頻繁に PUSH や POP を繰り返すたびに配列の生成や消滅を繰り返すことになってしまいます。

 そこで、今回は、PUSH で配列を拡張する時は、ReDim Preserve を使い、POPでは、値のみを削除するようにします。
 尚、そのままですと値のある配列サイズが取得できませんので、スタック用のデータサイズを監視する変数を用意しておきます。
 
 一応、配列をサイズ通りに生成し直す関数も用意しておき、必要に応じて配列サイズを作り直せるようにしておきます。

クラスの作成

 クラスの作成は、プロジェクトウインドウから右クリック、「挿入」>「クラスモジュール」で追加します。
 クラス名を CStack とします。
 クラス名は、プロジェクトウインドウでクラスを選択してから、プロパティウインドウの「オブジェクト名」の項目を書き換えます。

 では、コードウインドウにクラスを書いていきましょう。

メンバ変数定義

 まずはメンバ変数をて意義します。
 今回は文字列型の動的配列とスタックサイズを保持する整数型の変数をメンバとします。

'/// CStackのクラスモジュールに記述します ///
Private sArr() As String
Private mSize As Long

初期化関数 Initialize関数

 続いて、初期化の Class_Initialize を書きます。イベントプロシージャを選ぶ時と同じ要領で、コードウインドウの上のタブから選択します。

'/// CStackのクラスモジュールに記述します ///
Private Sub Class_Initialize()
    ReDim sArr(0)
    mSize = 0
End Sub

 先程のメンバを初期化しておきます。
 ReDim sArr(0) は、1個の要素を持つ配列ですが、mSize は 0 にしておきます。

アクセッサ関数

 スタックサイズを得るためのアクセッサ関数を書きます。
 クラスでは、メンバ変数に直接アクセスさせずにアクセッサ関数を通して値を取得するようにします。
 値を受け取る関数しか書きませんので、外側からメンバ変数を書き換える事が出来なくなります。

'/// CStackのクラスモジュールに記述します ///
Public Function stackSize()
    stackSize = mSize
End Function

PUSH データを格納する関数

 スタックに値を追加するpushコマンドです。
 最初にコードを示します。

'/// CStackのクラスモジュールに記述します ///
Public Sub push(s As String)
    If UBound(sArr) = 0 And sArr(0) = "" Then   '※1
        sArr(0) = s
        mSize = 1
    ElseIf UBound(sArr) + 1 > mSize Then      '※2
        sArr(mSize) = s
        mSize = mSize + 1
    Else                           '※3
        ReDim Preserve sArr(UBound(sArr) + 1)
        sArr(UBound(sArr)) = s
        mSize = mSize + 1
    End If
End Sub

 pushコマンドは、文字列データを引数として受け取ります。
 これを動的配列に追加するのが主な役割となります。

 If文でケースごとに3つに分岐します。

 ※1は、最初に値を追加する時の処理です。
 初回は、既に要素を1つだけ持つ配列を宣言していますので、そこに値を入れてサイズを1にしています。
 
 ※2は、後述します。
 ※3では、動的配列を1つ拡張して最後の要素に値を入れて、サイズを1増やしています。
 
 最後に※2ですが、既にPOPを繰り返して配列の要素数がスタックのサイズより大きい時に、最後の要素の後に値を入れて、スタックサイズを1増やしています。

POP データを取り出す関数

 スタックから最後のデータを取り出すのがpopコマンドです。
 popはデータを返した後、そのデータを削除します。

'/// CStackのクラスモジュールに記述します ///
Public Function pop() As String
    Dim s As String
    s = ""
    If mSize = 0 Then
        Err.Raise 1001, "Stack", "スタックにデータがありません"
    End If
    s = sArr(mSize - 1)
    sArr(mSize - 1) = ""
    mSize = mSize - 1
    
    pop = s
End Function

 データを返すので、Sub ではなく Function になります。
 文字列型を返すので、文字列型変数 s を用意して、最後に
   pop = s
 として、文字列を返します。
 一番最初に、If文でサイズが 0 の時に pop した時にエラーとなるような処理を書いています。
     Err.Raise 1001, “Stack”, “スタックにデータがありません”
 こうする事で、システムが発するようなエラーを出してくれます。

 以下のプログラムは、 pop 回数が多いためエラーを発生させるプログラム例です。
 このコードは、標準モジュールに書きます。

'/// Module1 //////////////
Sub errorTest()
    Dim stk As CStack
    Set stk = New CStack

    stk.push "F"
    stk.push "G"
    Debug.Print stk.pop
    Debug.Print stk.pop
    Debug.Print stk.pop
    
End Sub

 以下のようなエラーが出ます。

 もし、ここのエラーを回避したいのであれば、pop コマンドの前に stackSize関数でスタックサイズを確認してから pop するようにすると良いでしょう。

スタック配列を作り直す

 スタック配列にあまり変動が無いようであればこのコマンドは必要ないかもしれませんが、メモリを節約したい場合などに実行すれば良いでしょう。

'/// CStackのクラスモジュールに記述します ///
Public Sub resetStackArr()
    Dim i As Long
    Dim temp() As String
    ReDim temp(mSize - 1)
    
    For i = 0 To mSize - 1
        temp(i) = sArr(i)
    Next i
    
    ReDim sArr(mSize - 1)
    
    For i = 0 To mSize - 1
        sArr(i) = temp(i)
    Next i
    
    Erase temp
End Sub

 ここでは、一旦 temp という配列を mSize の分だけ確保して、値をコピーし、スタック配列(sArr)を定義し直してから値を戻しています。最後に、 temp を消滅させています。

CStackクラスの使い方

 クラスが完成したので、標準モジュールでこのクラスを使ってみましょう。

Sub test()
    Dim stk As CStack
    Set stk = New CStack
    
    stk.push "F"
    stk.push "G"
    stk.push "H"
    stk.push "I"
    stk.push "J"
    
    Debug.Print stk.pop
    Debug.Print stk.pop
    
    Debug.Print stk.stackSize
    
    Stop
    
    stk.push "K"
    
    Stop
    
    stk.resetStackArr

    Stop
    
End Sub

 クラスを使用するには、以下のように宣言してクラス変数を初期化します。

Dim クラス変数名 As クラス名
Set クラス変数名 = New クラス名

 
 初期化は、New を付けます。 またオブジェクトなので Set も忘れないようにしましょう。
 初期化の時に、Initialize関数が実行されます。

 クラスを使うときは、クラス変数名に続き、 . (ドット)関数名で実行します。
 尚、実行できるのは、Public属性を付けたものだけです。

 今回は、Stop コマンドで処理を止めてローカルウインドウで値を確認してみようと思います。

 まず、pushで F,G,H,I と値を入れていきます。これで 5個の配列要素が確保されます。
 つぎに、2回 pop を実行していますので、J, I と出力されます。
 次の stackSizeコマンドが返すのは 3 です。

 ここで Stop した時の配列の様子を見てみましょう。

 sArr配列は5個分の要素がありますが、mSizeは 3 となっています。
 続いて push コマンドで K を追加します。

 図のようにsArr配列は5個のままで、最後から2つ目に K が格納されました。
 mSizeは 4 となっています。
 最後にresetStackArr関数で配列を作り直します。

sArr配列が 4個。mSizeは 4 となりました。

スタックの使い道

 スタックは、配列の要素数に関係なく push, pop コマンドで値を抜き差しできます。
 プログラムで複雑な事をしない人にはあまり縁が無いかもしれませんが、木構造などのデータ処理を行うときなどに使われます。

タイトルとURLをコピーしました