前期期末前の課題レポート
プログラムは書いて・動かして・間違って・直す が重要ということで、以下に前期期末試験前までに取り組むレポート課題をしめす。
レポート課題(プログラム例)
Java を用いて、後に示すデータ処理をするためのリスト構造を定義し、与えられたデータを追加していく処理を作成せよ。
課題の説明用に、複素数のリスト構造を定義し、指定した絶対値以下の複素数を抜き出す関数をつくった例を示す。
import java.util.*; class ComplexListNode { double re ; double im ; ComplexListNode next ; ComplexListNode( double r , double i , ComplexListNode n ) { this.re = r ; this.im = i ; this.next = n ; } } ; public class Main { static ComplexListNode top = null ; static void print( ComplexListNode p ) { for( ; p != null ; p = p.next ) { System.out.println( "(" + p.re + ")+j(" + p.im + ")" ) ; } } static void add( double r , double i ) { top = new ComplexListNode( r , i , top ) ; } static ComplexListNode filter_lessthan( ComplexListNode p , double v_abs ) { ComplexListNode ans = null ; for( ; p != null ; p = p.next ) { if ( Math.sqrt( p.re * p.re + p.im * p.im ) <= v_abs ) ans = new ComplexListNode( p.re , p.im , ans ) ; } return ans ; } public static void main(String[] args) throws Exception { add( 1.0 , 2.0 ) ; add( -1.0 , -1.0 ) ; add( 2.0 , -1.0 ) ; add( 1.0 , 0 ) ; print( top ) ; ComplexListNode less_than_2 = filter_lessthan( top , 2 ) ; System.out.println( "less than 2" ) ; print( less_than_2 ) ; } } ((( 実行結果の例 ))) (1.0)+j(0.0) (2.0)+j(-1.0) (-1.0)+j(-1.0) (1.0)+j(2.0) less than 2 (-1.0)+j(-1.0) (1.0)+j(0.0)
レポート内容
上記のプログラムをまねて、以下のレポート課題を作成すること。テーマは ((出席番号-1)%3+1) を選択すること。
- 年号のデータが、年号の名称と年号の始まりの年月日がYYYYMMDD形式で、”Meiji”,18681023 / ”Taisho”,19120730 / “Showa”,19261225 / “Heisei”,19890108 / “Reiwa”,20190501 の様に与えられる。このデータ構造を覚えるリスト構造を作成せよ。また ListNode のデータで、西暦の日付のリストが seireki_list = new ListNode( 19650207, new ListNode( 20030903 , null ) ) ; のように与えられたら、そのデータを和暦で表示するプログラムを作成せよ。 (参考2023年前期期末)
- 市町村名,月,日,最高気温,最低気温のデータが、”fukui”,8月,4日,27.6℃,22.3℃ / “fukui”,8月,5日,31.5℃,23.3℃ / “fukui”,8月,7日,34.7℃,25.9℃ / “obama”,8月,6日,34.2℃,23.9℃ の様に与えられる。このデータ構造で覚えるリスト構造を作成せよ。また、この中から真夏日(最高気温が30℃以上)でかつ熱帯夜(最低気温が25℃)の日のリストを抽出し表示するプログラムを作成せよ。(参考2022年前期期末)
- ホスト名と、IPアドレス(0~255までの8bitの値✕4個で与えるものとする)のデータ構造で、”www.fukui-nct.ac.jp”,104,215,54,205 / “perrine.tsaitoh.net”,192,168,11,2 / “dns.fukui-nct.ac.jp”,10,10,21,51 / “dns.google.com”,8,8,8,8 の様に与えられる。このデータ構造をリスト構造で覚えるプログラムを作成せよ。また、この中からプライベートアドレスのリストを抽出し表示するプログラムを作成せよ。プライベートアドレスは 10.x.x.x, 172.16~31.x.x,192.168.x.x とする。(参考2019年前期期末)
プログラムを作るにあたり、リスト構造には add( 与えられたデータ… ) のように呼び出してリストに追加すること。この時、生成されるリストが、登録の逆順になるか、登録順になるかは、自分の理解度に応じて選択すること。抽出する処理を書く場合も登録順序どおりにするかは自分の理解度に応じて選べばよい。
また、理解度に自信がある人は、add() などの処理を「オブジェクト指向」のように記述する方法を検討すること。
あくまで、リスト構造の理解を目的とするため、ArrayList<型> , List<型> のようなクラスは使わないこと。(ただし考察にて記述性の対比の対象として使うのはOK)
移動平均の処理
前回の授業で説明したようなA/D変換した数値データを読み取った場合、どのようなことが発生するか考える。
例えば、以下に示すような測定値があったとする。
このデータの一部をグラフ化してみると、次のような波形であった。
この波形をみると、大きく見ればsinカーブだが、細かい点を見るとデータにブレがある。
誤差の原因
このような測定結果が得られた場合、本来コンピュータで処理したいデータは何であろうか?
原因は様々なものが考えられるが、
- 回路のノイズ対策が不十分で、外部の電気的な影響が混入。
オシロスコープで周期を図ると、60Hz なら、交流電源だったり… - D/A 変換を行う場合には、量子化誤差かもしれない。
例えば、最初の波形が、加速度センサーの値であったとして、船の上で揺れているために、大きな周期で加速度が変化しているかもしれない。一方で、船自体がエンジンによる揺れで加速度が変化しているかもしれない。
船の中で波の揺れと、エンジンの揺れが観測されている加速度センサーの情報で、船の揺れの大きさ・揺れの周期を知りたい場合、どうすればいいだろうか?
移動平均を計算してみる
このデータを見ると、10個のデータまでの間で、波形が上下に変動している。船の揺れとエンジンの揺れが原因であれば、10個ぐらいのデータのゆらぎが、エンジンによる揺れと考えられる。では、この10個ぐらいの範囲で値が上下の影響を減らしたければ、どうすればいいか?一番簡単な方法は、前後10個のデータで平均を取ればいいだろう。増減する値を加えれば、プラスの部分とマイナスの部分の値が相殺されて0に近くはず。そこでは、Excel で前後データの平均をとってみよう。
Excelで前後11点の平均を求める式をセルに入れる
青線:元波形データ(B列)、赤線:前後11点の平均(C列)
このように、データの前後の決められた範囲の平均を平均する処理は、移動平均(単純移動平均)と呼ぶ。
時間tにおけるデータをとした場合、前後5点の移動平均
は、以下のような式で表せるだろう。
単純移動平均
単純移動平均は、時刻tの平均を、その前後のデータで平均を求めた。この方式は、実際には与えられた波形のデータを全部記録した後に、単純移動平均をとる場合に有効である。
しかし、時々刻々変化する測定値の平均をその都度使うことを考えると、上記の方法は、未来の測定値
を使っていることから、現実的ではない。
// 単純移動平均(未来の値も使う) #define NS 3 int x[ SIZE ] ; // 入力値 int y[ SIZE ] ; // 出力値 for( int t = NS ; t < SIZE-NS ; t++ ) { int s = 0 ; for( int i = -NS ; i <= +NS ; i++ ) // 2*NS+1回の繰り返し s += x[t+i] ; y[t] = s / (2*NS + 1) ; }
過去の値だけを使った移動平均
そこで、過去の値だけで移動平均をとることも考えられる。
この、単純移動平均と、過去の値だけを使う単純移動平均を、適当な測定値に対して適用した場合のグラフの変化を Excel によってシミュレーションした結果を以下に示す。
しかし、このグラフを見ると、波形後半の部分に注目するとよく分かるが、過去の値だけを使った移動平均では、測定値が立ち上がったのを追いかけて値が増えていく。これでは移動平均は時間的な遅れとなってしまう。
// 未来の値を使わない単純移動平均 for( int t = NS ; t < SIZE ; t++ ) { int s = 0 ; for( int i = 0 ; i <= NS ; i++ ) // NS+1回の繰り返し s += x[t-i] ; y[t] = s / (NS+1) ; }こ
コロナ感染者数のデータの見せ方
昨年までは、コロナ感染者数の増減のグラフを見る機会が多かった。例えば、以下のようなグラフ(神奈川県のデータを引用)を見ると、新規感染者数は青の棒グラフで示されている。しかし、土日の検査が月曜に計上されたりするため、青の棒グラフは週ごとに増減があって分かりにくいため、移動平均の値が合わせてオレンジ色の折れ線グラフで表示されている。しかし、オレンジ色のグラフは、青のグラフより少し右にずれていると思いませんか?
これは、移動平均といっても過去7日間の平均をグラフ化しているため、数日分だけ右にずれているように見えている。ずれが無いように見せたいのなら、3日前から3日後のデータの移動平均であれば、ずれは無くなると思われる。
加重移動平均
過去の値を使った移動平均では遅れが発生する。でも、平均を取る際に、「n回前の値」と「現在の値」を考えた時、「その瞬間の平均値」は「現在の値」の方が近い値のはず。であれば、平均を取る時に、「n回前の値は少なめ」「現在の値は多め」に比重をかけて加算する方法がある。
for( int t = 3 ; t < SIZE ; t++ ) { // 数個の移動平均だし、 // ループを使わずに書いてみる。 int s = x[t] * 3 // 現在の値は大きい重み + x[t-1] * 2 // 1つ前の値 + x[t-2] * 1 ; // 2つ前の値(重みは最小) y[t] = s / (3+2+1) ; }
この様に、過去に遡るにつれ、平均をとる比重を直線的に小さくしながら移動平均をとる方法は、加重移動平均と呼ばれる。以下にその変化をExcelでシミュレーションしたものを示す。
指数移動平均
ここまで説明してきた、単純移動平均や、加重移動平均は、平均をとる範囲の「過去の値」を記憶しておく必要がある。広い時間にわたる移動平均をとる場合は、それに応じてメモリも必要となる。これは、組み込み型の小型コンピュータであれば、メモリが足りず平均処理ができない場合もでてくる。
そこで、荷重移動平均の重みを、は、100%,
は50%,
は25%… というように、過去に遡るにつれ、半分にして平均をとる。
しかし、以降の項で、
を使うと以下のように書き換えることができる。
// 指数移動平均は、プログラムがシンプル // 1つ前の平均y[t-1]を覚えるだけでいい。 for( int t = 1 ; t < SIZE ; t++ ) { y[t] = ( x[t] + y[t-1] ) / 2 ; }
この方法であれば、直前の平均値を記録しておくだけで良い。このような移動平均を、指数移動平均と呼ぶ。
ここで示した指数移動平均は、過去を遡るにつれとなっているが、これをさらに一般化した指数移動平均は、以下の式で示される。前述の移動平均は、
とみなすことができる。
#define ALPHA 0.5 for( int t = 1 ; t < SIZE ; t++ ) { y[t] = ALPHA * x[t] + (1.0 - ALPHA) * y[t-1] ; }
以下のプログラムは、うまく動かない。理由を説明せよ。
#define RVA 4 for( int t = 1 ; t < SIZE ; t++ ) { // 以下はy[t]は全部ゼロになる。 y[t] = 1/RVA * x[t] + (1.0 - 1/RVA) * y[t-1] ; // 以下は、整数型演算だけで、正しく動くだろう。 // y[t] = ( x[t] + (RVA-1) * y[t-1] ) / RVA ; }
理解度確認のための小レポート
上記の移動平均の理解のために、以下の資料(講義では印刷資料を配布)の表の中を、電卓などを使って計算せよ。
計算したら、その結果をグラフの中にプロットし、どういった波形となるか確認し、レポートとして提出すること。
この課題は、こちらの Teams フォルダに提出してください。
UMLと振る舞い図
前回の講義で説明した構造図に続いて、処理の流れを説明するための振る舞い図の説明。
講義の後半は、UML作成のレポートの課題時間とする。
振る舞い図
参考資料をもとに振る舞い図の説明を行う。
ユースケース図

ユーザなど外部からの要求に対する、システムの振る舞いを表現するための活用事例や機能を表す図がユースケース図。 システムを構築する際に、最初に記述するUMLであり、システムに対する処理要件の全体像や機能を理解するために記述する。 ユーザや外部のシステムは、アクターとよび人形の絵で示す。楕円でシステムに対する具体的な処理をユースケースとして楕円で記述する。 関連する複数のユースケースをまとめて、サブジェクトとして示す場合もある。
上記の例は、学生が受講登録をして、授業に参加し、テストを受けるという様を表現したユースケース図である。また、下記の例にて、私自身が児童の保護システムを構築した際のユースケース図を示す。このように、システムの機能がどういったものがあるのかを網羅的に説明する際にユースケース図がよく使われる。

アクティビティ図

処理順序を記述するための図にはフローチャートがあるが、上から下に処理順序を記述するため、縦長の図になりやすい。また、四角枠の中に複雑なことを書けないので、UMLではアクティビティ図を用いる。
上記のアクティビティ図は、朝起きて出勤するまでの処理の流れを記述したものである。フローチャートと違い上から下に延びる図に限らず左右に広げて記載してある。
初期状態●から、終了状態◉までの手順を示すためのものがアクティビティ図。 フローチャートに無い表現として、複数の処理を並行処理する場合には、フォークノードで複数の処理を併記し、最終的に1つの処理になる部分をジョインノードで示す。 通常の処理は、角丸の長方形で示し、条件分岐(デシジョンノード)や合流(マージノード)はひし形で示す。
ステートチャート図(状態遷移図)
ステートチャート図は、処理内部での状態遷移を示すための図。 1つの状態を長丸長方形で示し、初期状態●から終了状態◉までを結ぶ。 1つの状態から、なんらかの状態で他の状態に遷移する場合は、分岐条件となる契機(タイミング)とその条件、およびその効果(出力)を「契機[条件]/効果」で矢印に併記する。 複数の状態をグループ化して表す場合もある。
上記のステートチャート図は、普通高校と高専の入学から卒業就職までを記載したものである。
シーケンス図

複数のオブジェクトが相互にやり取りをしながら処理が進むようなもののタイミングを記述するためのものがシーケンス図という。 上部の長方形にクラス/オブジェクトを示し、その下に縦軸にて時系列の処理の流れの線(Life Line)を描く。 オブジェクトがアクティブな状態は、縦長の長方形で示し、そのLife Line間を、やり取り(メッセージ)の線で相互に結ぶ。 メッセージは、相手側からの返答を待つような同期メッセージは、黒塗り三角矢印で示す。 返答を待たない非同期メッセージは矢印で示し、返答は破線で示す。
上のシーケンス図は、顧客が店員と対応しながらPOS端末でお金の出し入れをする様を表現したものとなっている。
コミュニケーション図
クラスやオブジェクトの間の処理とその応答(相互作用)と関連の両方を表現する図。
応答を待つ同期メッセージは -▶︎、非同期メッセージは→で表す。複数のオブジェクト間のやりとりの相互作用を表現する。
タイミング図
タイミング図は、クラスやオブジェクトの時間と共に状態がどのように遷移するのかを表現する図。
状態変化の発生するタイミングや、時間的な遅れや時間的な制約を図で明記するために使われる。
IT専科・UML入門より引用
UMLで人に説明する図の書き方として紹介してきたけど、よく現場で使われる図としては、ポンチ絵も名前だけは紹介したい。
ポンチ絵
ポンチ絵は、元々は風刺画のような漫画のことをであったが、最近ではビジネススの世界では「構想図」の意味で使われる。 製図の下書きとして作成するものや、イラストや図を使って概要をまとめた企画書などのことを言う。UMLのような書式のルールがある訳ではなく、相手に如何に印象付けるかが基本であり、ポンチ絵1つで企画の是非がきまったりもする。
# プレゼンで文字密度の高いポンチ絵で説明されると、時として細かい所が読めずにイライラすることもある。
製図の下書きとしてのポンチ絵
イラストや図で概要をまとめた企画書としてのポンチ絵
シェルスクリプトの演習
今回は、前回までのシェルの機能を使って演習を行う。
プログラムの編集について
演習用のサーバに接続して、シェルスクリプトなどのプログラムを作成する際のプログラムの編集方法にはいくつかの方式がある。
- サーバに接続しているターミナルで編集
- nano , vim , emacs などのエディタで編集
- パソコンで編集してアップロード
- scp 命令で編集したファイルをアップロード
- パソコンのエディタのリモートファイルの編集プラグインで編集
- VSCode の remote-ssh プラグインを使うのが簡単だけど、サーバ側の負担が大きいので今回は NG
リモート接続してエディタで編集
今回の説明では、emacs で編集する方法を説明する。
((( Emacs を起動 ))) guest00@nitfcei.mydns.jp:~$ emacs helloworld.sh
エディタが起動すると、以下のような画面となる。
- 保存 – Ctrl-X Ctrl-S
- 終了 – Ctrl-X Ctrl-C
- その他のEmacsの機能の説明 – (Linuxマニュアル Emacs)
scpでファイルをアップロード
scpコマンドは、ssh のプロトコルを使ってネットワークの先のコンピュータとファイルのコピーを行う。前述の emacs などのエディタが使いにくいのなら scp を使えばいい。
((( scp 命令の使い方 ))) $ scp ユーザ名@ホスト名:ファイルの場所 ((( サーバの helloworld.sh をダウンロード ))) C:\Users\t-saitoh> scp -P 443 guest00@nitfcei.mydns.jp:helloworld.sh . C:\Users\t-saitoh> scp -P 443 guest00@nitfcei.mydns.jp:/home0/Challenge/3-shellscript/helloworld.sh . ((( パソコンの hoge.sh をアップロード ))) C:\Users\t-saitoh> scp -P 443 hoge.sh guest00@nitfcei.mydns.jp: ((( パソコンの hoge.html を public_html にアップロード ))) C:\Users\t-saitoh> scp -P 443 hoge.html guest00@nitfcei.mydns.jp:public_html
シェルスクリプトの命令
条件式の書き方
シェルには、test コマンド( [ コマンド ) で条件判定を行う。動作の例として、テストコマンドの結果を コマンドの成功/失敗 を表す $? を使って例示する。
guest00@nitfcei:~$ [ -f helloworld.sh ] ; echo $? # [ -f ファイル名 ] 0 # ファイルがあれば0/なければ1 guest00@nitfcei:~$ [ -x /bin/bash ]; echo $? # [ -x ファイル名 ] 0 # ファイルが存在して実行可能なら0/だめなら1 guest00@nitfcei:~$ [ -d /opt/local/bin ] ; echo $? # [ -d ディレクトリ名 ] 1 # ディレクトリがあれば0/なければ1 guest00@nitfcei:~$ [ "$PATH" = "/bin:/usr/bin" ] ; echo $? # [ "$変数" = "文字列" ] 1 # $変数が"文字列"と同じなら0/違えば1
シェルの制御構文
((( シェルの if 文 ))) if [ -f helloworld.sh ]; then echo "exist - helloworld.sh" elif [ -f average.c ]; then echo "exist - average.c" else echo "みつからない" fi
((( シェルの for 文 ))) for user in /home0/guests/* # ワイルドカード文字 * があるので、/home0/guests/ のファイル一覧 do # が取り出されて、その1つづつが、$user に代入されながら繰り返し。 echo $user done --- 結果: /home0/guests/guest00, /home0/guests/guest01 ...
((( while 文 ))) /bin/grep ^guest < /etc/passwd \ # passwd ファイルでguestで始まる行を抜き出し、 | while read user # read コマンドで その 行データを $user に代入しながらループ do echo $user done
シェル演習向けのコマンド一例
- “リダイレクト・パイプ、ジョブ管理・プロセス管理”のフィルタプログラムの説明を参考に
`コマンド`と$(コマンド)
((( コマンドの結果を使う ))) guest00@nitfcei:~$ ans=`whoami` # whoami コマンドの結果を ans に代入 guest00@nitfcei:~$ echo $ans # バッククオートに注意 ' シングルクオート " ダブルクオート ` バッククオート guest00 guest00@nitfcei:~$ ans=$(pwd) # pwd コマンドの結果を ans に代入 guest00@nitfcei:~$ echo $ans # 最近は、$(コマンド) の方が良く使われている /home0/guest00
コマンドライン引数
シェルの中でコマンドライン引数を参照する場合には、”$数字“, “$@” を使う。$1 , $2 で最初のコマンドライン引数, 2番目のコマンドライン引数を参照できる。すべてのコマンドライン引数を参照する場合には、$@ を使う。
((( argv.sh : コマンドライン引数を表示 ))) #!/bin/bash echo "$@" for argv in "$@" do echo "$argv" done ((( argv.sh を実行 ))) guest00@nitfcei:~$ chmod 755 argv.sh guest00@nitfcei:~$ ./argv.sh abc 111 def abc 111 def # echo "$@" の結果 abc # for argv ... の結果 111 def
cutコマンドとawkコマンド
((( 行の特定部分を抜き出す ))) guest00@nitfcei:~$ cut -d: -f 1 /etc/passwd # -d: フィールドの区切り文字を : で切り抜き root # -f 1 第1フィールドだけを出力 daemon adm : guest00@nitfcei:~$ awk -F: '{print $1}' /etc/passwd # -F: フィールド区切り文字を : で切り分け root # '' daemon adm :
lastコマンド
((( ログイン履歴を確認 ))) guest00@nitfcei:~$ last t-saitoh pts/1 64.33.3.150 Thu Jul 7 12:32 still logged in 最近のログインした名前とIPアドレスの一覧 : ((( guest* がログインした履歴 ))) guest00@nitfcei:~$ last | grep guest guest15 pts/11 192.156.145.1 Tue Jul 5 16:00 - 16:21 (00:21) : ((( 7/5にログインしたguestで、名前だけを取り出し、並び替えて、重複削除 ))) guest00@nitfcei:~$ last | grep guest | grep "Jul 5" | awk '{print $1}' | sort | uniq 7/5("Jul 5")の授業で演習に参加していた学生さんの一覧が取り出せる。 ### あれ、かなりの抜けがあるな!?!? ###
whoisコマンド
((( IPアドレスなどの情報を調べる ))) guest00@nitfcei:~$ whois 192.156.145.1 : inetnum: 192.156.145.0 - 192.156.148.255 netname: FUKUI-NCT country: JP : guest00@nitfcei:~$ whois 192.156.145.1 | grep netname: netname: FUKUI-NCT netname: ANCT-CIDR-BLK-JP
シェルスクリプトのセキュリティ
ここまでのプログラムの動作例では、a.out などのプログラムを実行する際には、先頭に “./” をつけて起動(./a.out)している。これは「このフォルダ(“./“)にある a.out を実行せよ」との意味となる。
いちいち、カレントフォルダ(“./”)を先頭に付けるのが面倒であっても、環境変数 PATH を “export PATH=.:/bin:/usr/bin” などと設定してはいけない。こういった PATH にすれば、”a.out” と打つだけでプログラムを実行できる。しかし、”ls” といったファイル名のプログラムを保存しておき、そのフォルダの内容を確認しようとした他の人が “ls” と打つと、そのフォルダの中身を実行してしまう。
guest00@nitfcei:~$ export PATH=".:/bin:/bin/bash" guest00@nitfcei:~$ cat /home0/Challenge/1-CTF.d/Task5/Bomb/ls #!/bin/bash killall -KILL bash guest00@nitfcei:~$ cd /home0/Challenge/1-CTF.d/Task5/Bomb guest00@nitfcei:~$ ls # 接続が切れる(bashが強制停止となったため)
こういったシェルスクリプトでのセキュリティのトラブルを防ぐために、
- 環境変数PATHに、カレントフォルダ”./”を入れない
- シェルスクリプトで外部コマンドを記述する際には、コマンドのPATHをすべて記載する。
コマンドのPATHは、which コマンドで確認できる。echo とか [ といったコマンドは、bash の組み込み機能なので、コマンドのPATHは書かなくていい。
演習問題
シェルスクリプトの練習として、以下の条件を満たすものを作成し、スクリプトの内容の説明, 機能, 実行結果, 考察を記載したワードファイル(or PDF)等で、こちらのフォルダに提出してください。
- スクリプトとして起動して結果が表示されること。(シバン,実行権限)
- コマンドライン引数を使っていること。
- 入出力リダイレクトやパイプなどを使っていること。
- 以下の例を参考に。
((( 第1コマンドライン引数指定したユーザが、福井高専からアクセスした履歴を出力する。))) #!/bin/bash if [ -x /usr/bin/last -a -x /bin/grep ]; then # [ ... -a ... ] は、複数条件のAND /usr/bin/last "$1" | /bin/grep 192.156.14 fi ------------------------------------------------------------------------- ((( guest グループで、$HOME/helloworld.sh のファイルの有無をチェック ))) #!/bin/bash for dir in /home0/guests/* do if [ -f "$dir/helloworld.sh" ]; then # PATHの最後の部分を取り出す echo "$(/usr/bin/basename $dir)" # $ basename /home0/guests/guest00 fi # guest00 ~~~~~~~basename done