わさっきhb

大学(教育研究)とか ,親馬鹿とか,和歌山とか,とか,とか.

変数の有効範囲の学び方

 来年度,後期に担当する学部1年向けのプログラミング科目は,クォーター科目として実施します.
 曜日・時限・教室は基本的に変わりません.より正確には,1コマ分,授業時間が増えます.10月からの8週が第3クォーター(3Q)になって1科目,そのあとに第4クォーター(4Q)として8週で1科目となります.合格したときの単位は,科目ごとに1単位ずつです.
 現在,シラバスの準備中です.内容について,今年度の(セメスター科目として実施した)内容を前半と後半に分けて1科目ずつ,というわけには,実はいきません.プログラミング科目に対する要望に対応する形で,授業計画を見直しています.
 少し手間を要するのは,授業の各回に何を学習するかだけでなく,「授業のねらい」や「到達目標」を,科目ごとに書く必要がある点です.とはいえ完全に別にする必要はなく,2つの文で構成して,第1文は3Q・4Qとも共通にし,第2文を異なるようにすれば,共通点と相違点を明確にできます.
 考えている案は,2科目ともC言語で実施し(VBAJavaScriptは今年度限りとなります),3Qの科目では「プログラミングの基本要素を学ぶ」「20行程度のプログラムコードを打ち込んで実行できる」,4Qの科目では「プログラミングについて理解を深める」「数十行程度のプログラムを実行して,処理内容などを説明できる」を目指すのが良さそうに思っています.
 今年度までの実施の反省を踏まえ,授業構想の一端を書いていきます.上ではC言語と書きましたが,Cのソースを書くと,将来的に実施する授業で本記事を検索する学生が出てくる可能性もありますので,以下ではPythonの自作プログラムを使用します.
 面白さを経験してもらっているプログラミング課題の一つに,「曜日計算」があります.「西暦年」「月」「日」の3つの整数値を入力にとり,wikipedia:ツェラーの公式を用いることで,0から6までのいずれかの整数値を求めます.ここで0は日曜日,1は月曜日,…,6は土曜日に対応します.以下のPythonスクリプトは,4を出力します---2020年12月31日は,木曜日です.ちなみにdowは"date of week"を元にした頭字語です.

year = 2020
month = 12
day = 31
if month <= 2:
    year = year - 1
    month = month + 12
dow = (year + year // 4 - year // 100 + year // 400
       + (13 * month + 8) // 5 + day) % 7
print(dow)

 授業では,このソースコードを画像にして,スライドの1枚に表示させ,時間をとって学生に打ち込ませます.if文の行末の「:」や,そのあとの行のインデントにも注意しながら,テキストエディタで作成して保存し,次に端末ソフトを前面に出してコマンド実行です.記号を打ち間違えた,ファイルがない*1といったトラブルには,ときには自分自身で,ときには周囲の助けを得て,解決していき,「4」だけが出力されるのを確認します.このプログラミング課題の最低条件をクリアしたところで,時間に余裕があれば,year, month, dayの値を変えて実行し直し,PCやスマートフォンのカレンダーと照合して,曜日が合っていることを確かめるといいでしょう.
 ツェラーの公式を使う場合には,1月・2月を「前の年の13月・14月」にしておくこと,それはこのプログラムではifから始まる3行で行っていることや,不等号は「≦」ではなく「<=」と書くこと,整数どうしの割り算の商を得るには「//」と書いているけれども他のプログラミング言語でそうとは限らないことなどは,このプログラミング作業の前か後ろに,教員が解説します.
 何コマかの授業において,さまざまな知識やプログラム例を学んだのち,曜日計算の処理を関数として括り出す課題を入れます.Cで昨年度および今年度に実施した課題の解答を踏まえると,うまく動作するプログラムは,次の2種類に大きく分かれます.

#!/usr/bin/env python3

def dayofweek(year, month, day):
    dow = (year + year // 4 - year // 100 + year // 400
           + (13 * month + 8) // 5 + day) % 7
    return dow

if __name__ == '__main__':
    year = 2020
    month = 12
    day = 31
    if month <= 2:
        year = year - 1
        month = month + 12
    dow = dayofweek(year, month, day)
    downame = ["Sunday", "Monday", "Tuesday", "Wednesday",
               "Thursday", "Friday", "Saturday"]
    print(downame[dow])
#!/usr/bin/env python3

def dayofweek(year, month, day):
    if month <= 2:
        year = year - 1
        month = month + 12
    dow = (year + year // 4 - year // 100 + year // 400
           + (13 * month + 8) // 5 + day) % 7
    return dow

if __name__ == '__main__':
    year = 2020
    month = 12
    day = 31
    dow = dayofweek(year, month, day)
    downame = ["Sunday", "Monday", "Tuesday", "Wednesday",
               "Thursday", "Friday", "Saturday"]
    print(downame[dow])

 見て分かるとおり,単純な書き換えではありません.先頭行(シバン)や,以降をメインの処理とするための「if __name__ == '__main__':」の行,そして,曜日を,数値ではなく文字列の配列(リスト)にしてそこから一つを取り出すのは,学生が思いつくものではなく,途中の授業で教員側から提示します.
 さて2つのプログラムの違いは,1月・2月を「前の年の13月・14月」にする処理をどこで行っているかです.上に書いたほうでは,メイン処理の中で行います.下に書いたほうでは,自作関数・dayofweekの中で行います.
 出力は同じです.上記のソースコードであれば,どちらも「Thursday」です.year, month, dayの数値を変えて実行し直しても,同じ値にしたら,出力も同じです.
 プログラムの書き方として,どちらがよいかというと,下のほうです.機能面で違いを知るには,それぞれのプログラムの末尾に(print(downame[dow])の次の行として),以下のコードを付け加えます.

    print('%4d/%2d/%2d (%s)' % (year, month, day, downame[dow]))

 そして,「前の年の13月・14月」にする処理を行うようにします.yearとdayの値はそのままで,monthを1に変えます.

    month = 1

 そして実行すると,上のほうは「2019/13/31 (Friday)」,下のほうは「2020/ 1/31 (Friday)」となります.年月日の表記として適切なのは,もちろん後者です.
 出力が違った原因は,このprint関数を実行する時点での,変数yearとmonthの値です.上のプログラムは,メインの処理の中で「前の年の13月」としており,それらの変数の値が変わってしまっています*2.それに対し,下のプログラムで「前の年の13月」としているのは,関数dayofweekの中となっており,そこで変数yearとmonthの値が変わっていることは,メインの処理の同じ変数に影響しません.「変数の有効範囲が異なるから」と言い表すことができます.
 この違いは,というか曜日計算を関数として括り出す演習課題は,変数の有効範囲を学ぶ授業回で実施すればよいというわけです.CやPythonに限らず,手軽に使える高級言語ではいずれも,変数の有効範囲が考慮されています.プログラムのどこからでも使える変数はグローバル変数と呼ばれ,便利そうに見えるけれども,バグの原因となりやすいので避けるようにし,変数の有効範囲をなるべく小さくするのがよいプログラムであること,そして関数呼び出し元の変数(実引数)と呼び出し時に値が代入される変数(仮引数)の名前が同じであっても,有効範囲が異なるので別物となることも,学ぶ機会となるのです.
 といった次第で,関数にせずに,曜日計算をするプログラムの作成(打ち込み)と実行は,3Qの科目で,また関数の括り出しを伴う課題のほうは,4Qの科目の中で,実施できればと考えています.
 これにて一件落着…としたかったのですが,「今年度までの実施の反省」を書き忘れていました.昨年度と今年度のプログラミング授業では,曜日計算は第3回の授業中に打ち込んでもらい,関数の括り出しは,第7回,Cの授業の最終回の発展課題としていました.ツェラーの公式に基づく計算だけでなく,前の年の13月・14月にする処理も,関数の中に入れるほうがいいのですよといった解説を作成して,Moodleから読めるようにしても,受講者みんなが読んで理解してくれた,とはいかなかったのです.

*1:作業ディレクトリがプログラムファイルのあるディレクトリと異なる,というのは,毎年巡回していると見かけますし,来年度以降も,なくなることはないでしょう.

*2:「if month <= 2:」より前の行で,年月日の箇所,すなわち「2019/13/31 」までを出力し改行しない,という処理を書くことは可能ですが,そのように出力処理を分割することが,今回の不具合の解決方法として適切なのかどうか,考えるべきでしょう.