見出し画像

個サ作 #15 演習問題:あみだくじ 3 + カレンダーLv.6 前編

こんにちは。

前回はあみだくじ2の実装を宿題としてお出ししたところで終わりました。

ご自身で考えて実装してくれましたでしょうか。いろんなロジックを試してみて、自分の力で解にたどり着ければそれに越したことはありませんが、みなさんは忙しい現代人です。

特にチャレンジもせず、#14からすぐにこちらのページを開かれていても、それはそれでOKです。

それでは答え合わせをしていきましょう。今回はあみだくじを例としていますが、何かシステムを組みたいとき、どんな風に思考を進めればいいのか?という観点から解説します。

また、このあみだくじが終わるとVBAでの基礎学習編も残りはカレンダーのLv.6とLv.7(7.5)を残すところのみとなります。

では、今回もよろしくお願いします。


あみだくじ 後編

ヒントソースの確認

まず、前回の補足の項でヒントとして以下のソースコードを案内しました。

Sub J_あみだくじ2()

    Call init
    
    If tryLotIndex = 0 Then
        Exit Sub
    End If
    
    '塗り替える色の取得
    Dim colors() As Long: colors = getColors()
    repaintColor = colors(tryLotIndex - 1)

    'あみだくじ進行を続けるかどうかのフラグ
    Dim isContinue As Boolean: isContinue = True
    
    Do
        '1セル進む
        tryLotRange.Select
        tryLotRange.Interior.Color = repaintColor
        
        If tryLotRange.Offset(0, 1).Interior.Color <> NO_COLOR  Then
            '右へ移動
            Set tryLotRange = tryLotRange.Offset(0, 1)
        ElseIf tryLotRange.Offset(0, -1).Interior.Color <> NO_COLOR Then
            '左へ移動
            Set tryLotRange = tryLotRange.Offset(0, -1)
        ElseIf tryLotRange.Offset(1, 0).Interior.Color <> NO_COLOR Then
            '下へ移動
            Set tryLotRange = tryLotRange.Offset(1, 0)
        Else
            '移動先がないので処理を終了する
            isContinue = False
        End If

        Application.Wait [Now() + "0:00:00.001"]
        
    Loop While isContinue

End Sub

ここまではあみだくじ1と同じだ、という話でしたね。こちらのソースはあみだくじ1と違ってWithを使っていませんが、このままいきます。

まったくの未実装だ、という方は上記のソースコードをJ_あみだくじ2に反映してください。

上記のソースコードで実行するとあみだくじ1の途中の段階と同じなので、下図のようなバグがあるままです。

あかんパターン

前回やりましたね。これをどうにかこうにかして、正しい動きにしよう!というのが宿題でした。

では、上記ソースに必要なものを加えていきます。

(ご自身なりにソースコードを実装された方は適当なマクロ名を別に付けてそれはそれで置いておいてくださいね。大切な成果ですので)


完遂への攻略方法

さて、とある機能を実現したいという状況が今目の前にあります。

くじを引いたときに進むのが右なのか左なのか上なのか下なのか、これを適切にプログラムに判断させて、あみだくじを正しい着地点まで導かねばなりません。

ここで、注目してほしいのが処理の規則性です。

例えば・・・

1, 3, 5, 7, 9, 11, 13, 15 ,17…

という数字の羅列があった場合、あなたはすぐにピンとくるでしょう。これがどういう規則によって用意された数字群なのか。

はい、そうですね。2ずつカウントアップしています。奇数の集合体とも言えますね。つまり17の次に来る数字は19です。

では、

1, 2, 4, 7, 11, 16, 22, 29, 37, 46…

こちらはいかがでしょう。46の次に来る数字は?はい、そうです。56です。カウントアップする数値が1ずつ上がっている、という規則がありますね。

今取り上げた2つの数値の羅列はプログラミングを駆使して出力することもできます。なぜか?規則性があるからです。規則性があればそれをプログラムの力でシステムに教えてやればいいだけです。

この理屈でカレンダーも実装してきました。世界のなべあつについても「3の倍数と3がつく数字の時だけ違う処理をしてくれ!」ということをシステムに教えてあげましたね。

では、これに倣ってあみだくじの中にある規則を考えてみましょう。

下にも右にも左にも上にも進行するくじをゆっくり引いている。なんかかわいい

上図は下、右、左、上それぞれの方向への進行パターンがあるくじをゆっくりと引いている様子です。

このルールに従えばどんな線の組み合わせが来ても対応できる!というロジックがあるはずなんです(#13でお伝えした作図ルールは守った上でね)。

ではね、4方向あるわけだからひとつずつ取り上げて考えてみましょうか。まずは下方向から。

くじを引いたときの動きを見てもらうとわかりますが、まず、最初に進むのは必ず下なんですよね。#13でお伝えした作図ルールに

  • 開始セル、終了セルから伸びる横線を書かない

というものがありますから、最初の進行は下以外ありえません

そして、進行が始まるとどうでしょう。

  • 一つの縦線につき、同じ位置から左右に伸びる線を書かない

というルールも事前に敷いていますから、右の道があったら右へ行くし、左の道があったら左へ移動する、という動きをします。左右が同時に現れて迷う、ということもありえません

では上はどうですかね。下に移動した次に上へ移動する、という動きはあみだくじのルールからしてありえないですね。元来た道を戻ることになりますから、この動きはしません

つまり、ここまでの話をまとめるとこういう↓ことになります。

  • 下へ移動した次に進行できる方向は右、左、下のいずれか

  • 下へ移動した次に上へは進行できない


で、先ほどひとつずつ取り上げてみましょうか、とは言いましたが、それをするとくどくなるので、今確認した下方向のルールに従い、他の方向についても次のようなことが言えます。

右だったら・・・

  • 右へ移動した次に進行できる方向は左、下、上のいずれか

  • 右へ移動した次に左へは進行できない

左だったら・・・

  • 左へ移動した次に進行できる方向は右、下、上のいずれか

  • 左へ移動した次に右へは進行できない

上だったら・・・

  • 上へ移動した次に進行できる方向は右、左、上のいずれか

  • 上へ移動した次に下へは進行できない


はい、ここまでで各方向別に進行方向に関する規則を列挙しました。

進行できる/できないというワードが何度も出てきましたね。ということは、各マスに進むたびに「今、〇方向へは進行できるのか、できないのか」を常に管理するのがよさそうです。


規則をソースコードで表現する

それでは、これをソースコードに落とし込んでみましょう。

次のソースを変数isContinueの宣言の次に書いてください。

    Dim isToRight As Boolean
    Dim isToLeft As Boolean
    Dim isToUp As Boolean
    Dim isToDown As Boolean: isToDown = True
赤枠が追加してほしい実装

4つの変数を追加しました。True/False(真偽値)を保持するBoolean型の変数名は「is」で始める、という話はもう何度もしてますね。そろそろ身に沁みついてきた頃でしょう。

前項の話と併せて、これら4つの変数がそれぞれ右、左、上、下へ進めるのかどうか?を管理する役割をもちます。ここにひとつポイントがあって4つの変数のうちisToDownだけは初期値をTrueとしています。

これは先ほど規則を考える中で「最初に進むのは必ず下」と述べました。ですので、Boolean型のデフォルト値はFalseになるため、下方向だけ進行可能だよ、という意味でTrueをあてています。


これら4変数が各方向への進行可否を握るということは、変数宣言後それぞれ2か所に追加してやる必要がありますね。

  • 各方向の進行可否を判定する条件式に組み込む

  • ある方向へ進んだ直後に次に進めるかどうかを設定する

この2つです。

ではまず、条件式に埋め込む方から実装したい・・・のですが、まだ上方向への分岐がない状態でしたね。先にこれを追加します。

        ElseIf tryLotRange.Offset(-1, 0).Interior.Color <> NO_COLOR Then
            '上へ移動
            Set tryLotRange = tryLotRange.Offset(-1, 0)
赤枠のソースコードを追記してほしい

はい、これで条件分岐としては右、左、下、上が揃いました。

ではですね、先ほど追加した4つの変数を条件式に組み込みます。

        If tryLotRange.Offset(0, 1).Interior.Color <> NO_COLOR And isToRight Then
            '右へ移動
            Set tryLotRange = tryLotRange.Offset(0, 1)

        ElseIf tryLotRange.Offset(0, -1).Interior.Color <> NO_COLOR And isToLeft Then
            '左へ移動
            Set tryLotRange = tryLotRange.Offset(0, -1)

        ElseIf tryLotRange.Offset(1, 0).Interior.Color <> NO_COLOR And isToDown Then
            '下へ移動
            Set tryLotRange = tryLotRange.Offset(1, 0)
            
        ElseIf tryLotRange.Offset(-1, 0).Interior.Color <> NO_COLOR And isToUp Then
            '上へ移動
            Set tryLotRange = tryLotRange.Offset(-1, 0)
各変数Trueならその方向に進行可能を表す

はい、このように追加してください。Boolean型の変数はそれ自体がTrueかFalseの値を持っているので、 = とか <> を用いた式を書く必要はありません。

ここまでくるともう簡単なんですよ。今、

  • 各方向の進行可否を判定する条件式に組み込む

を実装しましたから、あとは先ほどお伝えした

  • ある方向へ進んだ直後に次に進めるかどうかを設定する

を実装するだけです。そしてどう実装するかももう考えてある!先ほど整理した以下です。

  • 下へ移動した次に進行できる方向は右、左、下のいずれか

  • 下へ移動した次に上へは進行できない

  • 右へ移動した次に進行できる方向は左、下、上のいずれか

  • 右へ移動した次に左へは進行できない

  • 左へ移動した次に進行できる方向は右、下、上のいずれか

  • 左へ移動した次に右へは進行できない

  • 上へ移動した次に進行できる方向は右、左、上のいずれか

  • 上へ移動した次に下へは進行できない

この通りに各変数にTrue or Falseを設定するだけでいいんです!ちょっと長いけど、以下のようになります。

        If tryLotRange.Offset(0, 1).Interior.Color <> NO_COLOR And isToRight Then
            '右へ移動
            Set tryLotRange = tryLotRange.Offset(0, 1)
            isToLeft = False
            isToUp = True
            isToDown = True

        ElseIf tryLotRange.Offset(0, -1).Interior.Color <> NO_COLOR And isToLeft Then
            '左へ移動
            Set tryLotRange = tryLotRange.Offset(0, -1)
            isToRight = False
            isToUp = True
            isToDown = True

        ElseIf tryLotRange.Offset(1, 0).Interior.Color <> NO_COLOR And isToDown Then
            '下へ移動
            Set tryLotRange = tryLotRange.Offset(1, 0)
            isToUp = False
            isToRight = True
            isToLeft = True
            
        ElseIf tryLotRange.Offset(-1, 0).Interior.Color <> NO_COLOR And isToUp Then
            '上へ移動
            Set tryLotRange = tryLotRange.Offset(-1, 0)
            isToDown = False
            isToRight = True
            isToLeft = True
赤枠のソースコードを追記してね

こうです。先ほど文章で案内したことをソースコードでやってます。各分岐、今来た方向はFalseにしてそれ以外はTrueを設定しています。

では、もうやっちゃいましょう。いざ、動作確認。


動作確認

ほーら。できた

わーすごいすごい。できてますね。

じゃあちょっと遊び心出してもう少し拡張版もやってみますか。ソースコードじゃなくてくじの方ね。

余談だけど、今一括実行すると各くじ2回引くじゃないですか。それが煩わしいなってときは・・・

赤枠のところをコメントアウトすると1度のみの実行にできる

ここの2行をコメントアウトしてください。これ、呼び出す処理を愚直に2回してるだけなので、そこを1回分コメントアウトすればOKです。

では、私は

こんな作図にしてみました。あなたも好きにしてね

これでやってみます!いざ!

最後の1本が最右から最左にいくのがアツイ━━━━(゚∀゚)━━━━!!

うおおおおおおおおおおおおおおおおおお!!!!!!!!すげええええぇぇぇぇぇええええ!!!!!!!!!!!!

でも配色をちょっとミスってますね。なんか似たような色が続くと見にくいな。まぁいいか。

今回、#13の作図段階で

  • 縦線は10本まで

と、お願いしているのは、getColors関数で用意した色が10色だからです。

しかし、処理としては何本あろうがくじは引けるように作っているので、くじの数だけ色も用意すればたとえ100本あろうが1000本あろうが処理は動くようになっています。

あみだくじ、完!成!です!おつかれっした!


私からのプレゼント

今回の演習問題「あみだくじ」はなかなか遊び心に溢れた取り組みだったかと思います。

これまでのカレンダーや世界のなべあつにみられた、ただ値を出力するだけ、というものに比べると自分で作図したり色を設定したり、クリエイティブな要素も強めの課題でした。

実装を2度行うことで、ある程度実装イメージを掴んだ上で「宿題」として完成形にもっていくところをご自身で取り組んでいただきましたね。

プログラミングを学ぶ過程としてはすごく好奇心や創造力を掻き立てる道筋が用意できたのではないかと、その上を歩いていただけたのではないかと、自負しております。

これを登山としましたならば、この「あみだくじ演習問題」を達成したことに対し、何かご褒美やさらなる高みを想起させるプログラムに触れることができたなら、よりあなたの学習意欲が推進力をもつのは必至ひっしでしょう。


というわけで、私からあるソースコードをプレゼントさせてください。

前回#14にてクリア処理のソースコードをお渡ししましたが、そのマクロ名が「L_あみだくじ初期化1」となっているんですよね。「1」て。

2とか3があることをバリバリ意識したネーミングです。さらには#13の段階で

'最小行(拡張クリア処理で使用)
Private minRow As Integer
'最大行(拡張クリア処理で使用)
Private maxRow As Integer

こんな変数を用意しちゃってるんですわ。「拡張クリア処理」って言っちゃってる。

そういうわけなので、「工夫したらこんなこともできるんだぜ」というのを知っていただけたらと思います。クリア処理の2~5です。

ソースコードはまとめてテキストファイルで渡すので、動作だけご覧ください。中身をモジュールに貼ればあなたの環境でも動きます(ソースコードのコメントが少ないです、ごめんなさい)。


クリア処理2

ボタンを追加しました。高速一括くじ引きからの流れでご覧ください。

どや

こちらは薄い膜を貼り付けてから剝がすようなイメージですね。剥がすときに各くじの色も元に戻っています。

これ、くじが5本程度の時はあまり気にならなかったのですが、10本にもなるとちょっとパフォーマンスが気になりますね・・・。

この場合の「パフォーマンス」というのは処理速度のことです。IT業界全体で通用する使い方です。


クリア処理3

今度は色付きのシートを貼り付けてぺたーっと剥がしていくようなエフェクトです。

どやどや

これ、どうやって実現するのか?という観点で見ると面白いんですよ。一旦塗りつぶすわけだから作図したものが消えちゃうんですよね。

でも、次に剥がしていったときは再生されています。すごい。


クリア処理4

これもなかなかトリッキーです。

どやどやどや

左右両方から同時に塗りつぶしを進め、交差したタイミングからクリアしていってます。

少し種明かしをすると塗りつぶしを進める過程はループ処理を使ってるんですね。でもこの場合、右からのループと左からのループ、つまり2つのループを同時に動かしていることになる・・・と思いますか?

いいえ、そう見せかけているだけです。要はひとつのループの中で右からの処理も左からの処理もしてるんです。

答え合わせはソースを見てみてください。


クリア処理5

クリア処理3では一度全部塗りつぶしてから、改めてそれを剥がしていますが、これはそのハイブリット。色を塗りつぶす処理のすぐ後ろからクリアしていく処理が迫ってくる、とこんな↓動きになります。

どやりんこ

だめだ・・・パフォーマンスが悪すぎるな・・・。私のパソコンのスペックが低いからなのかな・・。これ動画の時間が長くなると容量の問題かアップできなくて、間を削りました。

実際はよろよろと線がなぞったくじを初期化していきます。

(あとでわかったのですが、皆様にお届けする用に動画を撮っていることも動きが悪い一因みたい)


はい!あみだくじはこれでおしまいです!ちょっと長丁場でしたが、いい気分転換および学習になったと思います。

次、いきましょう!


カレンダーLv.6 前編

はい、気持ちを切り替えて残りのカレンダーをやります。下図が完成形の動作です。

カレンダーLv.6の完成形の動作

これまでは縦にずらーっと日を羅列していましたが、今回は週ごとに横に日付を並べていきます。

新しいシートでやるので、まずはそちらを用意しましょう。


事前準備

この操作を真似してね

新しくシートを追加し、名前を「カレンダー2」にします。その後、以下のことをしてください(上図を参考に)。

  • 表示タブから「目盛線」チェックをOFF

  • B2セルを選択し・・・

    • 太字設定(「B」をクリック)

    • 値の書式を「文字列」に設定

    • フォントサイズを20に設定

    • フォントの種類を「BIZ UDPゴシック」に設定

  • 縮尺を70%に変更([Ctrl]キーを押しながらマウスのホイールを回す)

OK。では次にモジュールです。

この操作を参考にどうぞ

今、Study4のモジュールが最新なので、Study5を作ります。プロジェクトウィンドウ上で右クリックをし、挿入 > 標準モジュールと選択しましょう。

新しいモジュールができたらプロパティウィンドウより名前を設定します。上図を参考にしてください。

最後に側を作りましょう。もう一緒にモジュールレベル変数の宣言もやっちゃいます。

'年
Public year As Integer
'月
Public month As Integer
'日
Public day As Integer
'曜日
Public dayOfWeek As Integer
'開始列
Private startColumn As Integer
Sub Q_カレンダー6()

End Sub
赤枠つけてないけど、このようにあなたのモジュールにも反映してね

はい、このようにできたら準備OKです。モジュールレベル変数については、コメントをつけていることもあり、説明は割愛しますね。それぞれその通りの用途です。

と、モジュールStudy4の関数aがまだ生きている(コメントアウトしていない)場合は、コメントアウトしておいてください。実行時のマクロ選ぶときに出てきちゃうので。


初期化処理

最初に初期処理用の関数を用意します。こちらは今までやってきたこととかなり似通ったパターンです。

一気にソース書いちゃいましょう。

'月間カレンダー出力用初期処理
Function calendarInit()
    year = getRegexpFirstHit(Cells(2, 2).Value, "^\d{4}(?=年)")
    month = getRegexpFirstHit(Cells(2, 2).Value, "([1-9]|1[0-2])(?=月)")
    day = 1
    startColumn = 0

    If year = 0 Or month = 0 Then
        MsgBox "年月の値が不正です。"
        Exit Function
    End If
    
    '初期値、初期状態設定
    dayOfWeek = Weekday(year & "/" & month & "/" & 1)
    startColumn = dayOfWeek + 1
    Range("B3:J400").Clear
End Function
赤枠のソースコードを反映してね

はい、ありがとうございます。解説しましょう。

まずは最初の4行

    year = getRegexpFirstHit(Cells(2, 2).Value, "^\d{4}(?=年)")
    month = getRegexpFirstHit(Cells(2, 2).Value, "([1-9]|1[0-2])(?=月)")
    day = 1
    startColumn = 0

変数year, month, dayに関してはカレンダーLv.5でも同じことをしています。正規表現を使って各値を抽出する、というものですね。うまく抽出できなかったら値は0になります。

そして変数dayは固定値1です。必ず1日目から出力するからですね。

そして、次の変数startColumnは名前からして出力を始める最初の列番号を持つ変数です。この時点ではまだ未設定なので、0としています。

そして・・・

    If year = 0 Or month = 0 Then
        MsgBox "年月の値が不正です。"
        Exit Function
    End If

変数yearとmonthいずれかの値が0であればエラーだよと通知してこの関数を抜けちゃいます。関数を抜ける処理は「Exit Function」でしたね。

そして最後

    '初期値、初期状態設定
    dayOfWeek = Weekday(year & "/" & month & "/" & 1)
    startColumn = dayOfWeek + 1
    Range("B3:J400").Clear

はい、新しい関数のお出ましだ。Weekdayというのがいますね。

これは年月日の情報を渡したら曜日を示す値を返してくれる、という働きをします。年月日をどのように渡せるか、というと

バリアント型、数式、文字列式という記載がありますね。今回はyyyy/mm/dd形式の文字列で渡します。これ↓ね。

year & "/" & month & "/" & 1

関数の働きとして年月日から曜日を示す値を返してくれるとなると、ではどの曜日をどんな値で返してくれるのか?が気になりますね。

上記リンクの公式リファレンスから拝借すると・・・

曜日と値の対応付け

このようになっています。ですので、

    dayOfWeek = Weekday(year & "/" & month & "/" & 1)

この1行は上図の1~7のいずれかの値を受け取る処理をしています。変数dayOfWeekに代入していますね。

さらにこの受け取った値に1を足して変数startColumnに代入しています↓。

    startColumn = dayOfWeek + 1

この+1がどこから出てきたのかというと・・・

この最初の空き列の1列分の1なんです。今回最低でも2列目からのスタートという仕様にしていますからね。

最後の

    Range("B3:J400").Clear

この処理は読んでそのまま、セルの状態を初期状態に戻る(クリアする)処理をしています。これまでにも使いました。

B3セルからJ400セルってことは結構広い範囲をクリアしていますよね。これはカレンダーLv.7の年間カレンダー出力処理も同じシートでやるので、それも加味してちょっと大きめの範囲にしています。

はい、初期化関数としては以上です。

呼び出し側もあわせてここで処理を書いておきましょう。

この初期化関数calendarInitで出てきた変数のうち、変数startColumnはあみだくじでやった変数tryLotIndex似たような使い方をしています。

エラーチェックの前後で値を入れていますね。ということはこれも値が0ならエラー、それ以外だったらエラーじゃない、というマーカーとしての使い方ができます。

これを踏まえて次のソースコードをマクロの方に追加しましょう。

    Call calendarInit
    
    If startColumn = 0 Then
        Exit Sub
    End If
赤枠のソースコードを反映してね

初期化関数を呼び出すのも書いていただきました。はい、ここで変数startColumnが0だったら処理を終了する、としています。

エラーメッセージは初期化関数側で出しているのでここでは処理を抜ける(Exit Sub)だけでOKです。


二重ループの説明

今回のカレンダーは二重ループを使いますが、いきなり使ってもちょっと混乱するかなと思いまして、ここではカレンダーの実装は一旦離れて二重ループ処理の説明をします。

あみだくじ演習の時にクリア処理を案内しましたが、まだ二重ループを扱ったことがなかったので、ソースコードの解説は割愛すると言っていました。

それをここでさせてください。

Sub L_あみだくじ初期化1()
    firstRow = ActiveSheet.UsedRange.item(1, 1).Row
    lastRow = ActiveSheet.UsedRange.Rows.count + firstRow
    firstColumn = ActiveSheet.UsedRange.item(1, 1).Column
    lastColumn = ActiveSheet.UsedRange.Columns.count + firstColumn
    
    For i = firstRow To lastRow
        For j = firstColumn To lastColumn
            If Cells(i, j).Interior.Color <> NO_COLOR And Cells(i, j).Interior.Color <> YET_TRY_COLOR Then
            
                Cells(i, j).Interior.Color = YET_TRY_COLOR
                Application.Wait [Now() + "0:00:00.0001"]
            
            End If
        Next
    Next
End Sub

モジュールStudy4を参照ください。こんなソースコードでしたね。

最初の4行は外側のループの開始、終了値、内側のループの開始、終了値の4つの値を定義しています。つまり・・・

firstRow=10, lastRow=40, firstColumn=7, lastColumn=89 を取得している

この4つの値を取得しています。そして二重ループの構造というのはたいてい一次元方向の走査と二次元方向の走査を組み合わせることで実現します。

一次元、二次元という言葉は#14の配列の補足で扱ったとおりです。要は横方向と縦方向です。次の図は、クリア処理の様子をより視覚的にわかるようにしてみました。

最後1周余分に動いてる。ごめりんこ

下方向に1進む間に右方向には7列目から89列目を処理していっています。なので、n行目に対して全列を走査できたらn+1行に移ってまた全列を走査する、を繰り返しています。

縦方向の繰り返しと横方向の繰り返しを順に行うのが2重ループということですね。

ソースコード的には・・・

    For i = firstRow To lastRow
        For j = firstColumn To lastColumn
            If Cells(i, j).Interior.Color <> NO_COLOR And Cells(i, j).Interior.Color <> YET_TRY_COLOR Then
            
                Cells(i, j).Interior.Color = YET_TRY_COLOR
                Application.Wait [Now() + "0:00:00.0001"]
            
            End If
        Next
    Next

変数 i が行、変数 j が列を担当しています。jのループが内側なので、7列目(firstColumn)から89列目(lastColumn)を走査する間は変数 i はずっと変わりません。

変数 j の値が変数lastColumnの値にまで達したら、このループは終了します。そうすると外側の変数 i のループに移行するので、そこでやっと変数 i の値が +1されます。

ちなみに、今までループではカウンタ変数として変数 i を使ってきました。アルファベットの i です。今回 j も使っていますが、これはプログラミング界の慣例で決まっています。

ちなみに三重ループだと j の次は k を使います。四重ループは・・・知りません笑 三重ループはあっても四重ループは出会ったことがないですね。カウンタ変数 k は#18で扱います。


と、いうわけで、二重ループをご理解いただけたのではないかと思います。これを応用してカレンダーLv.6を実装していきましょう。

今回はここまでです。[Ctrl] + [S]キー押下で保存しておいてください。


今回の補足

あみだくじ2でWithを使っていない理由

この項は「そういう人もいるんだね」くらいで聞いてください。

#12, #14にて、Withステートメントって便利だよ、というニュアンスで使い方をお教えしました。でもあみだくじ2では私はあえてこれを使いませんでした。その理由をですね・・・語りたい。

理由は2つあります。

  • Withステートメントによってインデントが1段下がるから

  • オブジェクトを省略する記法により、直感的でなくなる

まずひとつめ。Withステートメントを使うことでインデントが1段下がる、です。SubやFunction、IfにFor、Do~While、これらは中に書くものは一段インデントを下げるという点が共通していますね。

この共通点においてWithをなんの抵抗もなく仲間入りさせられますか?私はね、ちょっと抵抗があるんですよ・・・。というのも上記に挙げた各ステートメントはインデントを下げることに意味があります。

そこに記載されるものがある目的を持った処理の連続であったり、ある条件を満たす時だけ実行される処理であったり、反復する処理であったり、インデントを下げるという行為がソースコードの秩序を守ることに寄与しています

ただ、その観点で言うとWithステートメントってプログラマの都合でしかないんですよね。基本的にソースコードを読解するとき、インデントが下がっているとそのこと(下がっている理由)を意識して読みます。

なにか条件分岐の中にあるのか、反復処理をしているのか、など。Withはコーディングの都合だけでインデントを下げてて他とはちょっと意味合いが異なるから使用に関して私はケースバイケースにしています。

もうひとつが直感的でなくなる、です。これはですね。

Withを使わなかったら、下図赤枠のように1か所にプロパティの記載が固まっているけれど・・・

これはあみだくじ2のソースから一部抜粋したもの

Withを使うと

これはあみだくじ1のソースから一部抜粋したもの

2か所になるじゃないですか。これ↑なんか近いところにあるからまだいいです。

Withを長い範囲にわたって使用し、Withステートメントが画面範囲外とかになるといちいちスクロールしないとどのオブジェクトが省略されているのかわからない、ということが起きてくるんですね。

なので、1行で完結した記載ができる方が良いのではないかというのが私の個人的な思いです。そうはいっても別に毛嫌いしてるわけではなく、使うときもあります。ケースバイケースです。

ちなみに、このWithステートメントにあたる記法、どのプログラミング言語にも用意されているわけではありません。そういう立ち位置のステートメントということです。


実装時の検索の仕方

これまでにいろんな組込関数やプロパティを使用してきました。MsgBox関数に始まりIsNumeric関数、Len関数、Split関数、UsedRangeプロパティ、Offsetプロパティ、今回やったWeekday関数など、さまざまです。

これらって必要になったときに私がポンと出しているので、特に疑いなく使っていただいています。でも、実際に自分がなにか機能を作りたいとき、分からないこと盛りだくさんですよね。

ですから、検索して目的の情報にたどり着く力が必要になります。私が関数やプロパティを案内するということはあなたが自力でそこに辿り着く機会を奪っているとも言えますからね。

検索ワードのテクニックをお教えします。

例えばOffsetプロパティの場合・・・

あみだくじの実装において近隣のセルを取得するのが目的でした。ですので「vba 隣のセル」と入れると上位にヒットした記事皆に「Offset」というワードが入ってますね。

検索の際、大前提となるフィールドの情報を必ず入れるようにしてください。今だったらあなたはプログラミング言語VBAで実装をしているんですよね。だからまず最初に「vba」を必ずつけましょう。これをつけないと

ちょっと検索結果がブレます。幸い「セル」という言葉がエクセルとセットみたいなものなので、Googleの検索エンジンが気を利かせて最初のヒットにOffset関数を出してくれていますが、精度は落ちるのでご注意ください。

OffsetプロパティにOffset関数・・・Offsetはプロパティなの?関数なの?というところでもブレがあります。これは検索精度の話もありますが、シンプルに世のプログラマたちもそこの理解が曖昧だ、という現実もあります。

公式は↑プロパティですね。

上記のようにこれまで何度も公式リファレンスを案内してきていますが、検索をしても公式リファレンスが上位にヒットするとは限りません。私も探すのに奔走することがたまにあります。

とはいえ、「vba 隣のセル」の検索結果から「Offset」というキーワードが見つかると、次は・・・

「vba offset」というキーワードで検索した様子

2番目には公式リファレンスがヒットしています。検索結果はGoogleの検索アルゴリズムやネットの海に存在する記事の数や質にも左右されます。

一度出した検索結果が未来永劫そのままということはないので、その点も頭に入れておいてください。


おわりに

おわりです。あみだくじおつかれさまでした!

以前やっていた連載「自作CMSの作り方」に目を通す機会があったのですが、やっぱり今回はVBAから基礎を学ぶ流れにしてよかったなと思っています。

丸腰の初心者がいきなりPHPに手を出して、本格的なシステムを作りながら同時にプログラミングの基礎も学ぶっていうのは盛沢山すぎてしんどかったんじゃないかなと感じたからです。

また、この基礎編を通して私の説明の仕方に慣れていただく狙いもあります。さらにプログラミングに対して自信をつけていただく狙いも。

基礎編もあと3回ですので、辛抱強くお付き合いいただけますと幸いです。

それでは糖分でも摂ってゆっくりなさってください。

今回もありがとうございました。


この記事が気に入ったらサポートをしてみませんか?