トランスポート層・TCPとUDP
前回の授業でブロードキャストなどの説明をしたことを踏まえ、DHCP について改めて説明
DHCP
前回の IP では、異なるサブネットを繋ぐ役割としての Internet Protocol (IP) について説明をした。IP での通信では、IPアドレスが必要だが、正しく接続ができるためには、(1)IPアドレス、(2)サブネットマスク、(3)ゲートウェイアドレス が必要となる。しかし、この情報は初心者には設定が難しいし、IPアドレスが他の利用者と重複させないためには、きちんとした管理が必要となる。
この時に使われるのが DHCP(Dynamic Host Configuration Protocol) であり、通常のパソコンは、IPの自動設定としておけば、DHCP を用いて前述の(1),(2),(3) の情報が自動で設定される。DHCP機能は、一般的にルータや WiFi の AP(アクセスポイント)の中に組み込まれている。
DHCPサーバには、使用可能なIPアドレスを登録しておく。利用者が DHCP クライアントとして接続する際には、ブロードキャストパケットを使い、同じサブネット内に DHCP リクエストを送る。このリクエストを DHCPサーバが受信したら、サーバはクライアントに向かって、貸し出し用の IP アドレスの1つをクライアントに提供する(サブネットマスクやゲートウェイなども提供)。
データ通信ではノイズなどの影響で通信に失敗することがある。これらを補うためのTCPがある。またTCPの通信の欠点を補うUDPがある。この授業では、TCPとUDPによるトランスポート層の説明を行う。
TCP
TCP(Transmission Control Protocol/トランスミッションコントロールプロトコル)では、分割されたパケットを元の順序に組み上げたり、パケットが途中で消えた場合の再送などの処理を行う。この機能により確実に相手に送る機能が実現されている。
3way ハンドシェーク
TCPの通信では、最初に相互に通信が可能かを確認するハンドシェークが行われる。パケットには、SYN,ACK,FINといった種別を表すフラグがついており、SYNは接続確立の要求を表す。ACKは了解を表す。FINは切断要求を表す。通信開始の時には、(1)通信OK?、(2)OKだよ,そっちもOK?、(3)OKだよ! といった3つの通信パケットで確認してから通信を行う。この最初のやり取りを3way ハンドシェークという。
- SYN flood攻撃 – 3wayハンドシェークは、この後に送られてくるパケットを並び替えるためのメモリを準備などが必要となる。このため通信ルールを無視して相手にSYNパケットだけを大量に送ると相手はムダな準備作業により他の通信が困難になる場合がある。
SEQ番号,ACK番号
通信パケットには、SEQ番号(シーケンス番号/32bit)とACK番号(アクノリッジ番号/32bit)という情報がついており、送信元はACK番号を確認することで、どのパケットが正しく届いたのかを認識できる。3wayハンドシェーク時には、相手のSEQ番号に1を加えたACK番号をつけてパケットを返送することで通信が始められることが確認できる。実際のデータを送信する際には、受け取ったデータ長をSEQ番号に加えた値を、ACK番号にして受信に成功したことを相手に伝える。これにより、小分けにされたパケットで次に何を送れば良いのか判別できる。
(Acknowledge = 承認する)
通信で、パケット分割して送って、その一つ毎の返答を待つと、通信の待ち時間が増えてしまう。このため、相手が受け取り可能であれば、一度に前回の2倍のパケットを返信を待たずに送る。(ウィンドウサイズの拡大)
チェックサムとタイムアウト
通信では、送る途中でデータにノイズが混入したり、パケットが消失することがある。このため、パケットにはパケットのチェックサム(バイトデータを加算した値)を付けて送り、受信時に比較してノイズ混入でデータが壊れていないかを確認する。こういった際には、パケットが正しく届かない。パケットが消失したりして、通信相手からの返送が届かないで一定の待ち時間が経過することをタイムアウトと呼ぶ。この時、返信パケットにはデータのSEQ番号とACK番号の情報があるため、受け取りに失敗したパケットが判別できるので、送り側は失敗したパケットを再送する。
受け取り側は、同じくSEQ番号やACK番号を元にパケットの順番を正しく並べ戻すことができる。
TCP FINパケット
通信を切断する場合には、相互に切断して良いか確認する4回の通信で終了する。
UDP
TCPによる通信は、相手側からの受け取った返事を待ちながら通信を行う。このため、通信にかかる時間を要する。また、複数の利用者に一斉にデータをばらまくブロードキャスト通信では、個別のパケット欠落を修復しようとすると、処理が複雑になる。
これらの対応策として、UDP(User Datagram Protocol)がある。これは、TCP通信でのパケット分割や再送処理を行わない極めて単純な送信方法である。このため、相手側に正しくデータが送られる保証はない。確実に相手に送る必要があれば、確認や再送は上位プロトコルの責任となる。
UDP通信は返信を待つ必要がないので、動画・音声配信などのリアルタイム性が求められる通信でよく使われる。UDPは正しく通信ができずパケットが途絶えても、一時的に動画が止まるなり音声が止まるといったように、問題が少ないような場合に有用となる。
ICMP/ping
IPプロトコルのプロトコルの1つとして ICMP (Internet Control Message Protocol) がある。このプロトコルは、ネットワーク機器(ノード)の間で、通信の確認をするためのもので、ping コマンドや traceroute コマンドで使われる。
基本的に、ICMPパケットを相手コンピュータに送り、返事が返ってくるかを確認する。ping や traceroute は、返事が返ってくるまでの時間や、パケットをいくつ送っていくつ返ってきた…などの情報を表示することができ、相手コンピュータまでの通信路が正常かどうかが判断できる。
$ ping www.google.co.jp PING www.google.co.jp (172.217.25.163) 56(84) バイトのデータ 64 バイト応答 送信元 syd09s13-in-f3.1e100.net (172.217.25.163): icmp_seq=1 ttl=115 時間=7.85ミリ秒 64 バイト応答 送信元 syd09s13-in-f3.1e100.net (172.217.25.163): icmp_seq=2 ttl=115 時間=8.02ミリ秒 ^C # 途中で強制終了させるために Ctrl-C で止める ]$ traceroute www.google.co.jp traceroute to www.google.co.jp (172.217.25.163), 30 hops max, 60 byte packets 1 airstation.ei.fukui-nct.ac.jp (192.168.2.1) 0.355 ms 0.529 ms 0.549 ms (略) 9 108.170.243.33 (108.170.243.33) 8.245 ms 108.170.243.65 (108.170.243.65) 6.893 ms 6.936 ms 10 72.14.239.25 (72.14.239.25) 6.899 ms 7.125 ms 72.14.238.23 (72.14.238.23) 7.140 ms 11 syd09s13-in-f163.1e100.net (172.217.25.163) 7.014 ms 7.007 ms 6.961 ms
トランスポート層
OSI参照モデルでは、TCPプロトコルとUDPプロトコルをあわせてトランスポート層と呼び、TCP+UDPとネットワーク層のIPプロトコルでの通信が、今日のインターネット通信の基本プロトコルとなっており、総称して TCP/IPとかインターネット・プロトコル・スイート と呼ぶ。(suite=”一連のものや一緒に機能するものの集まり”/sweetじゃない)
木構造の探索・深さ優先探索・幅優先探索
深さ優先探索(前順)
以前紹介した2分木の表示では、再帰呼び出しで木の処理を記述していた。
しかし、スタックを使って未処理のノードを保存しながらノードに対する処理を行えば、ループ処理で「すべての木のノード」に対する処理を記述できる。このような木に対する処理は、末端方向に処理が進むことが優先されるため深さ優先探索と呼ぶ。
以下のプログラムで示す深さ優先探索では、ノードに対する処理を行ってから左の枝葉の処理が行われる。このような深さ優先探索は前順(pre-order)と呼ぶ。
このような探索は、ゲームですべての戦略の先読みを行う処理で用いられる。
import java.util.*; class BTreeNode { int data ; BTreeNode left ; BTreeNode right ; BTreeNode( int x , BTreeNode l , BTreeNode r ) { this.data = x ; this.left = l ; this.right = r ; } } public class Main { // 再帰呼び出しによる木の操作 = 深さ優先探索(前順) public static void print_rec_depth_first_pre_order( BTreeNode p ) { if ( p != null ) { System.out.print( p.data + " " ) ; print_rec_depth_first_pre_order( p.left ) ; print_rec_depth_first_pre_order( p.right ) ; } } // 深さ優先探索(前順) public static void print_depth_first_pre_order( BTreeNode p ) { // 未処理ノードを保存するスタック LinkedList stack = new LinkedList<>() ; stack.addFirst( p ) ; // push while( !stack.isEmpty() ) { // スタックトップを取り出す p = stack.removeFirst() ; // pop if ( p != null ) { System.out.print( p.data + " " ) ; // 枝を未処理スタックに保存 stack.addFirst( p.right ) ; // 右を後回し stack.addFirst( p.left ) ; } } } public static void main(String[] args) throws Exception { BTreeNode top = new BTreeNode( 42 , new BTreeNode( 27 , new BTreeNode( 13 , null , null ) , new BTreeNode( 38 , null , null ) ) , new BTreeNode( 72 , new BTreeNode( 64 , null , null ) , new BTreeNode( 81 , null , null ) ) ) ; // 再帰による深さ優先探索(前順) print_rec_depth_first_pre_order( top ) ; System.out.println() ; // スタックを使った深さ優先探索(前順) print_depth_first_pre_order( top ) ; System.out.println() ; } }
- 2分探索木の深さ優先探索、幅優先探索(Paiza.io)
幅優先探索
同じように、未処理のノードを待ち行列に保存しながら、ノードに対する処理を行うことでも、すべての木に対する処理が記述できる。この場合、根本の処理をすべて終えてから、末端に対する処理が行われる。このような処理は幅優先探索と呼ぶ。
幅優先探索の処理と、前順の深さ優先探索を比べると、ノードを覚えるデータ構造に待ち行列を使う(幅優先探索)か、スタックを使う(深さ優先探索)の違いしかない。
![]() |
![]() |
幅優先探索は、ゲームの戦略で先読みを行う場合に用いられるが、チェス・将棋・囲碁といったすべての先読みを行うと手数が爆発的に増加するため、不利な戦略の先読みを途中で打ち切る必要がある場合に有用である。
public class Main { // 幅優先探索 public static void print_breadth_first( BTreeNode p ) { // 未処理ノードを保存する待ち行列 LinkedList queue = new LinkedList<>() ; queue.addFirst( p ) ; // put while( !queue.isEmpty() ) { // 待ち行列の先頭を取り出す p = queue.removeLast() ; // get if ( p != null ) { System.out.print( p.data + " " ) ; // 枝を未処理待ち行列に追加 queue.addFirst( p.left ) ; // 左から処理 queue.addFirst( p.right ) ; } } } public static void main(String[] args) throws Exception { BTreeNode top = (略) ; // 幅優先探索で表示 print_breadth_first( top ) ; System.out.println() ; } }
深さ優先探索(中順)
2分探索木のようなデータで、昇順で並べて表示する必要がある場合、再帰呼び出しのプログラムでは、「左枝の処理、ノードへの処理、右枝の処理」の順で記述する必要がある。このような深さ優先探索は中順(in-order)と呼ばれる。しかし、先に説明した再帰による深さ優先探索(前順/pre-order)では、「ノードへの処理、左枝の処理、右の枝の処理」で記述されている。
スタックを用いた深さ優先探索で、2分探索木の昇順のように中順で処理を行うには、未処理のノードの保存順序に工夫が必要となる。
import java.util.*; public class Main { // 再帰呼び出しによる木の操作 = 深さ優先探索(中順) public static void print_rec_depth_first_in_order( BTreeNode p ) { if ( p != null ) { print_rec_depth_first_in_order( p.left ) ; System.out.print( p.data + " " ) ; print_rec_depth_first_in_order( p.right ) ; } } // 深さ優先探索(中順) public static void print_depth_first_in_order( BTreeNode p ) { // 未処理ノードを保存するスタック LinkedList stack = new LinkedList<>() ; for( ;; ) { // ノードを保存しながら左末端まで進む while( p != null ) { stack.addFirst( p ) ; p = p.left ; } // 未処理が残っていないなら終了 if ( stack.isEmpty() ) break ; // スタックトップを取り出す p = stack.removeFirst() ; // pop System.out.print( p.data + " " ) ; p = p.right ; } } public static void main(String[] args) throws Exception { BTreeNode top = (略) ; // 再帰による深さ優先探索(中順) print_rec_depth_first_in_order( top ) ; System.out.println() ; // スタックを使った深さ優先探索(中順) print_depth_first_in_order( top ) ; System.out.println() ; } }
2分木による構文木
コンパイラの処理の流れ
構文の構造を表すために、2分木を使うという話をこの後に行うが、その前にコンパイラが機械語を生成するまでの処理の流れについて説明をする。
Cコンパイラのソース ↓ プリプロセッサ (#define,#includeなどの処理) ↓ コンパイラ ・字句解析(ソースコードをトークンに切り分ける) ・構文解析(トークンから構文木を生成) ・最適化(命令を効率よく動かすために命令を早い命令に書き換え) ・コード生成(構文木から中間コードを生成) | | リンカでライブラリと結合 (+)←---ライブラリ ↓ 機械語
バイトコードインタプリタ
通常、コンパイラとかインタプリタの説明をすると、Java がコンパイラとか、JavaScript はインタプリタといった説明となる。しかし、最近のこういった言語がどのように処理されるのかは、微妙である。
(( Java の場合 )) foo.java (ソースコード) ↓ Java コンパイラ foo.class (中間コード) ↓ JRE(Java Runtime Engine)の上で 中間コードをインタプリタ方式で実行
あらかじめコンパイルされた中間コードを、JREの上でインタプリタ的に実行するものは、バイトコードインタプリタ方式と呼ぶ。
ただし、JRE でのインタプリタ実行では遅いため、最近では JIT コンパイラ(Just-In-Time Compiler)により、中間コードを機械語に変換してから実行する。
また、JavaScriptなどは(というか最近のインタプリタの殆どPython,PHP,Perl,…は)、一般的にはインタプリタに分類されるが、実行開始時に高級言語でかかれたコードから中間コードを生成し、そのバイトコードをインタプリタ的に動かしている。
しかし、インタプリタは、ソースコードがユーザの所に配布されて実行するので、プログラムの内容が見られてしまう。プログラムの考え方が盗まれてしまう。このため、変数名を短くしたり、空白を除去したり(…部分的に暗号化したり)といった難読化を行うのが一般的である。
字句解析と構文解析
トークンと正規表現(字句解析)
字句解析でトークンを表現するために、規定されたパターンの文字列を表現する方法として、正規表現(regular expression)が用いられる。
((正規表現の書き方)) 選言 「abd|acd」は、abd または acd にマッチする。 グループ化 「a(b|c)d」は、真ん中の c|b をグループ化 量化 パターンの後ろに、繰り返し何回を指定 ? 直前パターンが0個か1個 「colou?r」 * 直前パターンが0個以上繰り返す 「go*gle」は、ggle,gogle,google + 直前パターンが1個以上繰り返す 「go+gle」は、gogle,google,gooogle
正規表現は、sed,awk,Perl,PHPといった文字列処理の得意なプログラム言語でも利用できる。こういった言語では、以下のようなパターンを記述できる。
[文字1-文字2...] 文字コード1以上、文字コード2以下 「[0-9]+」012,31415,...数字の列 ^ 行頭にマッチ $ 行末にマッチ ((例)) [a-zA-Z_][a-zA-Z_0-9]* C言語の変数名にマッチする正規表現
構文とバッカス記法
言語の文法(構文)を表現する時、バッカス記法(BNF)が良く使われる。
((バッカス記法)) <表現> ::= <表現1...> | <表現2...> | <表現3...> | ... ;
例えば、加減乗除記号と数字だけの式の場合、以下の様なBNFとなる。
((加減乗除式のバッカス記法)) <加算式> ::= <乗算式> '+' <乗算式> 【要注意】わざと間違っている部分あり | <乗算式> '-' <乗算式> | <乗算式> ; <乗算式> ::= <数字> '*' <乗算式> | <数字> '/' <乗算式> | <数字> ; <数字> ::= [0-9]+ ;
上記のバッカス記法には、間違いがある。”1+2+3″を正しく認識できない。どこが間違っているだろうか?
このような構文が与えられた時、”1+23*456″と入力されたものを、“1,+,23,*,456”と区切る処理が、字句解析である。
また、バッカス記法での文法に合わせ、以下のような構文木を生成するのが構文解析である。
+ / \ 1 * / \ 23 456
2項演算と構文木
演算子を含む式が与えられたとして、古いコンパイラではそれを逆ポーランド変換して計算命令を生成していた。しかし最近の複雑な言語では、計算式や命令を処理する場合、その式(または文)の構造を表す2分木(構文木)を生成する。。
+ / \ 1 * / \ 2 3
演算子の木のノードで、末端は数値であることに注目し、右枝・左枝がNULLなら数値(data部にはその数値)、それ以外は演算子(data部には演算子の文字コード)として扱うとして、上記の構文木のデータを作る処理と、その構文木の値を計算するプログラムを示す。
import java.util.*; class ExpTreeNode { String op_val ; ExpTreeNode left ; ExpTreeNode right ; ExpTreeNode( String op , ExpTreeNode l , ExpTreeNode r ) { this.op_val = op ; this.left = l ; this.right = r ; } ExpTreeNode( String val ) { this.op_val = val ; this.left = null ; this.right = null ; } } public class Main { public static int eval( ExpTreeNode p ) { if ( p.left == null && p.right == null ) { return Integer.parseInt( p.op_val ) ; } else { int l_val = eval( p.left) ; int r_val = eval( p.right ) ; switch( p.op_val ) { case "+" : return l_val + r_val ; case "*" : return l_val * r_val ; } return -1 ; // error } } public static void main(String[] args) throws Exception { ExpTreeNode top = new ExpTreeNode( "+" , new ExpTreeNode( "1" ) , new ExpTreeNode( "*" , new ExpTreeNode( "2" ) , new ExpTreeNode( "3" ) ) ) ; System.out.println( eval( top ) ) ; } }
- 2分木による構文木(Paiza.io)
オブジェクト指向を使った構文木(テスト範囲外)
前述の2分木を用いた構文木では、式を構成する「整数値」の式、「式 演算子 式」の演算式の二つのパターンを、無理やり1つのクラスで扱っていた。整数値と演算子の文字列の違いがあるため、数値も文字列で保存したものを parseInt() で数値に変換している。
こういった、大きくは式と分類できるけど、数値を表す式、演算子からなる式と異なるデータの場合、Java では、オブジェクト指向プログラミングの抽象クラスと仮想関数のテクニックを用いる。式を表現する基底クラス ExpNode から、数値を表す ExpNodeInteger , 演算子からなる式を表す ExpNodeOperator を派生させ、式の値を求める際には、各クラスに応じた計算処理を行う仮想関数 eval() を使って処理を行っている。
import java.util.* ; // 抽象基底クラス abstract class ExpNode { abstract public int eval() ; } // 整数で具体化 class ExpNodeInteger extends ExpNode { int value ; ExpNodeInteger( int i ) { this.value = i ; } // 仮想関数 public int eval() { return this.value ; } } // 演算子で具体化 class ExpNodeOperator extends ExpNode { String op ; ExpNode left ; ExpNode right ; ExpNodeOperator( String s , ExpNode l , ExpNode r ) { this.op = s ; this.left = l ; this.right = r ; } // 仮想関数 public int eval() { int l_val = this.left.eval() ; int r_val = this.right.eval() ; switch( this.op ) { case "+" : return l_val + r_val ; case "*" : return l_val * r_val ; } return -1 ; } } public class Main { public static void main(String[] args) throws Exception { ExpNode top = new ExpNodeOperator( "+" , new ExpNodeInteger( 1 ) , new ExpNodeOperator( "*" , new ExpNodeInteger( 2 ) , new ExpNodeInteger( 3 ) ) ) ; System.out.println( top.eval() ) ; } }
ERモデル
データベースの設計
リレーショナル・データベースでは、データは表形式であればなんでも良い訳ではない。
例えば、学生の成績データが以下のような1つのテーブルで構成されるデータだった場合、
|
- 修正不整合: 授業担当が 斉藤 → 高久 のように変更になったら、複数のテーブルを修正しなければならない。大量のレコード修正は、時間がかかるし、その途中にシステムダウンしたらデータの整合性に問題が発生するかも。
- 挿入不整合: 新しい科目 情報ネットワーク を追加したいけど、受講学生が決まらないとデータを挿入できない。
- 削除不整合: 山田 が受講を取りやめたら、科目 情報メディア も消えてしまう。
これらを考慮すると、以下のような3つの表で設計するべきである。
■学生(実体)
|
■受講(関係)
30203を消しても情報メディア |
■科目(実体)
斉藤→高久に変更したら 情報ネットワークを追加しても問題なし |
データベースの設計では、(1)概念設計、(2)論理設計、(3)物理設計が行われる。
- 概念設計:概念スキーマの決定(実体・関係モデルを使う)。上記の受講データベースの設計例
- 論理設計:論理スキーマの決定。関係データベースで実装?ほかのデータベース?
- 物理設計:物理スキーマの決定。データの格納方法や管理方法を決める。
実体関連モデル(ERモデル)
データベース設計では、実体関連モデル(ERモデル:Entity-Relation model)が使われる。 実体とは、モデル化しようとする対象で独立した存在となれるもの。 実体が持つ色々な特性は属性と呼ばれる。 属性の取りうる値の集合を定義域、同一種類の実体の集まりを実体集合と呼ぶ。 関連とは、実体同士の相互関係をモデル化したもの。
実体関連図(ER図)では、実体を長方形、関連をひし形、属性を楕円で表現する。 属性で、キーとなるものには下線をつけて表す。
ER図で調べると、実際にはもっと細かい規定で表現が行われている。 参考:IDEF1X表記とIE表記
専攻科実験・コンパイラと関数電卓プログラム作成
- コンパイラの技術と関数電卓プログラム(1)
- 再帰下降パーサによる構文解析(LL法)による電卓プログラム作成
- 補助資料:コンパイラの技術と関数電卓プログラム(1-2)
- 課題
- 複数桁の数字が使えること。
- 式中に空白が使えること。
- 何らかの演算子を追加すること。
- (例) %,単項演算子のマイナスなど
- 演算子が左結合か右結合か確認すること。
- オプション課題
- 変数が使えること。
(変数名は1文字のA-Zといったもので良い)
- 変数が使えること。
- レポート内容
- コンパイラ技術の概要、課題(1)の説明・最終的なBNF記法・ソース・動作検証、考察
- 前半レポート提出先
- コンパイラの技術と関数電卓プログラム(2)
- コンパイラツールを使ったLR構文解析による電卓プログラムの作成
- 補助資料:専攻科実験: unix系 開発環境のインストール
- 課題
- 基本的に、lex+yaccで(1)と同様の課題で参考資料を元に改良を行う。
- レポート内容
- lex,yaccの概要、課題(2)の説明・ソース・動作検証、考察
- 後半レポート提出先
2分木の応用(意思決定木と演算子)
データをO(log N)で検索するための2分探索木以外の2分木のデータ構造について解説を行う。
意思決定木
意思決定木の説明ということで、yes/noクイズの例を示しながら、2分木になっていることを 説明しプログラムを紹介。
((意思決定木の例:小さい子供が発熱した時)) 38.5℃以上の発熱がある? no/ \yes 元気がある? むねがひいひい? yes/ \no no/ \yes 様子をみる 氷枕で病院 解熱剤で病院 速攻で病院
このような判断を行うための情報は、yesの木 と noの木の2つの枝を持つデータである。これは2分木と同じである。左右に枝のあるものは質問であり、yesの枝もnoの枝もない末端は最終決断を表す。このようなデータ構造は意思決定木と呼ばれ、質問と決断の処理は以下のように記述ができる。
import java.util.* ; import java.util.Scanner ; class TreeNode { String qa ; TreeNode yes ; TreeNode no ; TreeNode( String s , TreeNode y , TreeNode n ) { this.qa = s ; this.yes = y ; this.no = n ; } } ; public class Main { public static void main(String[] args) throws Exception { Scanner sc = new Scanner( System.in ) ; TreeNode top = new TreeNode( "38.5℃以上の発熱がある?" , new TreeNode( "胸がひぃひぃ?" , new TreeNode( "速攻で病院" , null , null ) , new TreeNode( "解熱剤で病院" , null , null ) ) , new TreeNode( "元気がある?" , new TreeNode( "様子をみる", null , null ) , new TreeNode( "氷枕で病院", null , null ) ) ) ; TreeNode p = top ; while( p.yes != null || p.no != null ) { System.out.println( "Question:" + p.qa ) ; if ( sc.nextInt() == 1 ) { System.out.println( "yes" ) ; p = p.yes ; } else { System.out.println( "no" ) ; p = p.no ; } } System.out.println( "Answer:" + p.qa ) ; } }
- 意思決定木(Paiza.io)
struct Tree { char *qa ; struct Tree* yes ; struct Tree* no ; } ; struct Tree* dtree( char *s , struct Tree* l , struct Tree* r ) { struct Tree* n ; n = (struct Tree*)malloc( sizeof( struct Tree ) ) ; if ( n != NULL ) { n->qa = s ; n->yes = l ; n->no = r ; } return n ; } void main() { struct Tree* p = dtree( "38.5℃以上の発熱がある?" , dtree( "胸がひぃひぃ?" , dtree( "速攻で病院", NULL,NULL ) , dtree( "解熱剤で病院",NULL,NULL ) ) , dtree( "元気がある?" , dtree( "様子をみる", NULL,NULL ) , dtree( "氷枕で病院", NULL,NULL ) ) ) ; // 決定木をたどる struct Tree* d = p ; while( d->yes != NULL || d->no != NULL ) { printf( "%s¥n" , d->qa ) ; scanf( "%d" , &ans ) ; // 回答に応じてyes/noの枝に進む。 if ( ans == 1 ) // yesを選択 d = d->yes ; else if ( ans == 0 ) // noを選択 d = d->no ; } // 最終決定を表示 printf( "%s¥n" , d->qa ) ; }
2分木の応用として次週以降に式の表現の説明を行うけど、その前に計算式の一般論の説明を行う。
逆ポーランド記法
一般的に 1*2 + 3*4 と記載すると、数学的には演算子の優先順位を考慮して、(1*2)+(3*4) のように乗算を先に行う。このような優先順位を表現する時に、()を使わない方法として、逆ポーランド記法がある。
演算子の書き方には、前置記法、中置記法、後置記法があり、後置記法は、「2と3を掛ける、それに1を加える」と捉えると、日本語の処理と似ている。
中置記法 1+2*3 前置記法 +,1,*,2,3 後置記法 1,2,3,*,+ # 1と「2と3をかけた値」をたす。
後置記法は、一般的に逆ポーランド記法(Reverse Polish Notation)とも呼ばれ、式をコンピュータで実行する際の処理と似ている。
演算子の右結合・左結合
例えば、”1/2*3″という式が与えられたとする。この結果は、1/6だろうか?3/2だろうか?
一般的な数学では、優先順位が同じ演算子が並んだ場合、左側から計算を行う。つまり”1/2*3″は、”(1/2)*3″を意味する。こういった左側の優先順位が高い演算子は左結合の演算子という。
ただしC言語やJavaでは、”a = b = c = 0″ と書くと、”a = (b = (c = 0))” として扱われる。こういった代入演算子は、 右結合の演算子である。
理解度確認
以下の式を指定された書き方で表現せよ。
逆ポーランド記法 1,2,*,3,4,*,+ を中置記法で表現せよ。 中置記法 (1+2)*3-4*5 を逆ポーランド記法で表現せよ。
以前の情報処理技術者試験では、スタックの概念の理解の例題として、逆ポーランド記法への変換アルゴリズムのプログラム作成が出題されることが多かったが、最近は出題されることはなくなってきた。
ネットワーク層とIPアドレス
ネットワーク層とIPアドレス(IPv4)
(前回の復習)
サブネットに分割し、隣接するサブネット、さらには上流のインターネットと通信をするためには、IPアドレスを用いた通信が行われる。
ネットワークに接続する機器には、それぞれユニークな32bitの番号(IPv4アドレス)を割り振る。
コンピュータへのIPアドレスの設定には、(a)IPアドレス,(b)サブネットマスク,(c)ゲートウェイの情報が必要となる。
- IPアドレス: 192.156.145.100 といった、0~255の8bitの値をピリオド区切りで4つ並べて表記するのが一般的。
- サブネットマスク: 255.255.255.0 といった値で、IPアドレスを2進数で書き並べた32bitと、サブネットマスクの32bitで、2進数の論理積をとった値は、ネットワーク番号と呼ばれ、機器が存在する場所を表す。
また、IPアドレスとサブネットマスクの否定と論理積をとった値は、ホスト番号と呼ばれる。
サブネットマスクは、先行する1のbit数で書き表すことも多い。255.255.255.0は、”/24″のように書く。 - ゲートウェイ: 自分自身のネットワーク番号と通信相手のネットワーク番号が異なる場合は、異なるサブネットにいるので、パケットを中継してもらう機器(ルータ,ゲートウェイ)にパケットを送る。
- IPアドレスとクラス: IPアドレスは、先頭8bit をネットワーク番号とするクラスA,16bitのクラスB,24bitのクラスCに分類されている。以前は、IPアドレスを割り当てる企業規模に応じて、大規模な大学だからクラスA、中規模ならクラスB(福井大学は133.7.0.0/16 ←このような書き方はCIDR記法という)、小規模ならクラスCを割り当てていた。(福井高専はCクラスを5本192.156.145~149.0/24 : 福井高専のIPアドレスでは3つのCIDR記法で表現できる。 192.156.145.0/24, 192.156.146.0/23, 192.156.148.0/23)
しかし、最近では IPv4 アドレスの不足から、大きな組織に割り振られた クラスA を再分配しているため、先頭が0~126で始まるアドレスでも大きなネットワーク組織とは限らない。
ユニキャスト、マルチキャスト、ブロードキャスト通信
通信では、機器が1対1でデータを送る(ユニキャスト通信)だけでなく、複数の機器に同時にデータを送りたいこともある。
同じサブネット内の全てにデータを送る時には、ブロードキャスト通信が用いられる。この際には、IPアドレスのサブネットにおけるホスト番号部分が全て1のアドレスを使う。Cクラスの 192.168.11.x/24 のネットワークであれば、192.168.11.255 がブロードキャスト通信用のアドレスとなる。一般的にホスト番号部分が全て0のアドレスは、サブネット全体を表すために使われることから、192.168.11.0 のIPアドレスも通常の通信には使えない。このため、Cクラス(サブネットマスク24bit)であれば、256台-2台(全て0と全て1のホスト番号)を引いた254台の機器が設置できる。(実際には、他のネットワークに接続するためのゲートウェイアドレスも必要なので 253台)
動画などの配信では、ユニキャスト通信が使われると、サーバから通信相手までのルータまでに同じデータが送られてくる。こういった方式では大量の視聴者がいる場合、ネットワークの帯域が埋まってしまい、効率の良い配信ができない。同じデータを同じタイミングで複数の機器に配信したい場合はマルチキャスト通信を使う。IPv4では 224.0.0.0/4 を使う。
ARP(IPアドレスとMACアドレスの橋渡し)
同じサブネットの中では、データリンク層でMACアドレスを用いて通信相手を指定するが、ネットワーク層ではIPアドレスを用いて通信相手を指定する。この違いを埋めるためのプロトコルがARPである。
サブネット内に相手先IPアドレスの指定されたパケット(10.10.22.102)が届くと、通信機器はサブネット内の全ての機器相手に ARPリクエストを送信する。(10.10.22.102はいますか?ブロードキャスト通信を使う)
この時、10.10.22.102 のコンピュータは、自分宛てのパケットがあることを知るので、送信元のコンピュータに、自分のMACアドレスを付けたARPリプライを送り返す。(10.10.22.102は、私 “FE:DC:BA:98:76:54” です!)。送信元は、ARP通信をへらすために、その情報を記憶して、2度目以降は覚えたMACアドレスですぐに通信を始める。
ルータとRIP
ルータは、隣接するサブネットの間に入る機器で、各サブネットにゲートウェイとなるインタフェースを持つ。ルータの内部では受け取ったパケットをどこに流すか…という経路情報が重要となる。
- 静的ルーティング – 末端のルータの設定で、管理者が経路を設定
- 動的ルーティング – 上流ルータでRIPにより設定
経路設定には2種類あり、(a)末端のルータではこのネットワーク番号はどのルータに送るという情報をルータに直接設定する静的ルーティングがとられる。(b)上流のルータでは、末端のルータの設定が変更されることがあるので、ルータ同士がルーティング情報を送りあう動的ルーティングがとられる。動的ルーティングでは、RIPというプロトコルにより、各サブネットのつながっている経路情報が送られてくる。この経路情報を見て、パケットのIPアドレスを見て、パケットの送り先を判断する。(ネットワークの上流では、複数のネットワーク経路を操る BGP などが使われる)
((Windows の場合))
C:> ipconfig インタフェース名: IPv4アドレス............192.168.xx.xx サブネットマスク.........255.255.255.0 デフォルトゲートウェイ....192.168.xx.1 C:> arp -a インタフェース: 192.168.xx.xx 74-03-xx-xx-xx-xx 動的 192.168.xx.yy B0-05-xx-xx-xx-xx 動的 C:> netstat -r ネットワーク宛先 ネットマスク ゲートウェイ インタフェース メトリック 0.0.0.0 0.0.0.0 192.168.xx.1 192.168.xx.xx 45 192.168.xx.0 255.255.255.0 ....
((Unix の場合))
$ ifconfig -a # もしくは ip addr en1: .... inet 192.168.xx.xx netmask 0xffffff00 ... $ arp -an # もしくは ip neigh .... (192.168.xx.xx) at 74:03:xx:xx:xx:xx ... .... (192.168.xx.yy) at b0:05:xx:xx:xx:xx ... $ netstat -rn # もしくは ip route Destination Gateway ... default 192.168.xx.1 ... 192.168.xx ...
プライベートアドレス
IPv4 では、32bit でコンピュータを識別することから、最大でも 232台≒40億台(4×103×3台)しか識別できない。実際、IPアドレスの管理団体では、2017年度には IPv4 アドレスは使い切った状態となっている。この対応として、その組織やその家庭内だけで使われる IPアドレス である、プライベートアドレスが用いられる。
- 10.0.0.0~10.255.255.255 / 8 – 大きな機関向け
- 172.16.0.0~172.31.255.255 / 12
- 192.168.0.0~192.168.255.255 /16 – 個人向け
プライベートアドレスを利用する組織では、インターネットに接続するルータでは NAT(もしくはNAPT) という機能を内蔵し、プライベートアドレスとグローバルアドレスの変換を行う。
ローカルループバックアドレス
ネットワーク通信では、1つのコンピュータの中でもプロセスとプロセスの間でデータ通信を行うことも多い。この時に使われるIPアドレスは、ローカルループバックアドレスと呼ばれ、IPv4では 127.0.0.1 のIPアドレスを使う。このアドレスには localhost というドメイン名が割り振られる。
IPv6アドレス
IPv4の32bit の IP アドレスでは、40億台のコンピュータを区別することしかできない。そこで、最近では 128bit の IPv6 アドレスが用いられる(256×102412台≒340×1036台)。 IPv6 では、2004:6800:4004:0826:0000:0000:0000:2003 (www.google.co.jp) といった16進数4桁(16bit)を8個を”:”区切りで書き連ねる書き方をする。ただし16進4桁の先行する0や、全部”0000″ は省略して、”2404:6800:4004:826::2003″ と書く。IPv6 は最近では普及がかなり進んでいるが、途中のルータなどがすべて IPv6 に対応する必要があり IPv4 しか使えない組織も多い。
IPv6でも、同じサブネット内だけで使えるIPv6リンクローカルアドレス(fe80::/10)や、同じ組織内だけで使えるIPv6サイトローカルアドレス(fec0::/10)、ループバックアドレス(::1)などが規定されている。
DNS と nslookup
IPアドレスみたいな数字の羅列は覚えることが難しい。このためコンピュータの名前からIPアドレスを調べるサービスがあり、Domain Name Service(DNS) と呼ばれる。詳しい仕組みは ドメイン名の説明の際に行うが、IP アドレスの調べ方を説明する。
ドメイン名から、IP アドレスを調べるには、nslookup (もしくは dig) を使用する。
$ nslookup www.fukui-nct.ac.jp : Non-authoritative answer: Name: www.fukui-nct.ac.jp Address: 104.215.53.205 $ nslookup -query=A www.fukui-nct.ac.jp # IPv4 アドレスの取得 (同上) # もしくは dig www.fukui-nct.ac.jp A +short $ nslookup -query=AAAA www.google.co.jp # IPv6 アドレスの取得 : # もしくは dig www.google.co.jp AAAA +short Non-authoritative answer: Name: www.google.co.jp Address: 2404:6800:4004:826::2003
理解確認
- Formsによる理解度確認問題
- 192.168.11.2/24 のコンピュータから、192.168.1.50にデータを送る場合、どのような処理が行われるか、IPアドレス、サブネットマスク、ゲートウェイ、ネットワーク番号を使って説明せよ。
- 同じサブネット内で相手のIPアドレスが与えられた時、どのようにパケットが送られるか、MACアドレスとARPを交えて説明せよ。
GROUP BY HAVINGとビューテーブル
GROUP BY HAVING
GROUP BY-HAVING では、指定されたカラムについて同じ値を持つレコードがグループ化される。SELECT 文に指定される集約関数は、グループごとに適用される。HAVING は、ある条件を満たす特定のグループを選択するための条件で、WHERE と違い、集約関数が使える。
SELECT SG.商品番号, SUM(SG.在庫量) FROM SG GROUP BY SG.商品番号 HAVING SUM(SG.在庫量) >= 500 ;
このSQLを実行すると、SG のテーブルから、商品番号が同じものだけをあつめてグループ化される。そのグループごとに在庫量のデータの合計SUMを集約し、合計が500以上のデータが出力される。
|
⇒ |
|
⇒ |
|
CREATE VIEW
今までで述べてきたSQLでは、実際のテーブルを対象に、結合・選択・射影を行う命令であり、これは概念スキーマと呼ばれる、対象となるデータベース全体を理解したプログラマによって扱われる。
しかし、プログラムの分業化を行い、例えば結果の表示だけを行うプログラマにしてみれば、全てのデータベースの表を考えながらプログラムを作るのは面倒である。そこで、結合・選択・射影の演算の結果で、わかりやすい単純な表となったものであれば、初心者のデータベースプログラマでも簡単に結果を扱うことができる。このような外部スキーマを構成するための機能が、ビューテーブルである。
-- 優良業者テーブルを作る -- CREATE VIEW 優良業者 ( 業者番号 , 優良度 , 所在 ) AS SELECT S.業者番号, S.優良度, S.所在 FROM S WHERE S.優良度 >= 15 ; -- 優良業者テーブルから情報を探す -- SELECT * FROM 優良業者 WHERE 優良業者.所在 = '福井' ; -- 文房具データベース -- CREATE VIEW 文房具データベース ( 業者名 , 商品名 , 在庫量 ) AS SELECT S.業者名, G.商品名, SG.在庫量 FROM S, SG, G WHERE S.業者番号 = SG.業者番号 and SG.商品番号 = G.商品番号 ; -- VIEWの確認 -- SELECT * FROM 文房具データベース ; -- 串刺ししたデータベースに見える ----- 実行結果 ----- 業者名 商品名 在庫量 ABC社 赤鉛筆 300 ABC社 ノート 200 ABC社 消しゴム 400 ABC社 消しゴム 200 ABC社 筆箱 100 ABC社 バインダ 100 LMN社 赤鉛筆 300 LMN社 ノート 400 PQR社 ノート 200 STU社 ノート 200 STU社 消しゴム 300 STU社 筆箱 400
ビューテーブルに対する SQL を実行すると、システムによっては予め実行しておいた CREATE VIEW の AS 以下の SQL の実行結果をキャッシュしておいて処理を行うかもしれない。システムによっては SQL の命令を 副クエリを組合せた SQL に変換し、処理を行うかもしれない。しかし、応用プログラマであれば、その SQL がどのように実行されるかは意識する必要はほとんど無いであろう。
ただし、ビューテーブルに対する 挿入・更新・削除といった演算を行うと、データによっては不整合が発生することもあるので注意が必要である。
SQL言語
教科書の流れに沿ってSQLの言語について、再掲
- スキーマ定義
- CREATE – 実テーブル、ビューテーブルの定義
- GRANT – 権限の定義
- スキーマ操作
- DROP – 実テーブル、ビューテーブルの削除
- REVOKE – 権限の削除
- ALTER – テーブルの変更
- ADD – カラムの追加
- データ操作
- SELECT, INSERT, DELETE, UPDATE – レコードの検索、追加・削除・更新
- トランザクション処理
- データベースでは、原子性などを満たすためにデータベースへの更新履歴を保持している。これらの更新履歴をデータベースに反映させ確定する処理がトランザクション処理。
- COMMIT – データベースの更新処理を確定
- ROLLBACK – データベースの更新処理を取り消す
ホスト言語とのインタフェースとSQLインジェクション
プログラミング言語によっては、その言語の中でSQLを使うために「組み込み型のSQL」が使えるものがある。
(COBOL,PL/Iなど)
動的メモリ管理が得意な最近のPythonやPHPなどの言語であれば、データベース参照の関数が利用できる。
SQLインジェクション
例えば、PHPでは、SQLからデータを取り出す処理は、以下のようになる。
// 検索するユーザID $id = "t-saitoh" ; $pdo = new PDO( '...' ) ; // データベースに接続する関数 $sql = "select name from usertable where id='$id'" ; $query = $pdo->prepare( $sql ) ; // 取り出せたデータに関する処理 id がプライマリキーならforeachは1回ループのはず foreach( $query->fetcAll() as $name ) { // $name に取り出した名前が入っている }
しかし、$id の部分を、Web の入力フォームなどの値であれば、名前以外の情報が入力される場合もある。
この際に、「 $id = ” ‘ or 1==1 — ‘ ” 」といった値が入っていた場合、SQLで実行される命令は、
$id = "' or 1==1 --'" の場合 $sql = "select name from usertable where id='' or 1==1 -- ''" ;
となってしまい、本来なら1人のデータを抽出する select 命令が、全テーブルに対して該当してしまい、情報漏洩が発生するかもしれない。
「 $id = “‘; drop usertable ; — ‘” 」であれば、usertable が消されてしまい、システムが動かなくなる(サービスを提供できなくする攻撃 = DoS攻撃 – Denial-of-service attack)ことも考えられる。
他にもSQLインジェクションを使う技がある。こちらの CTF の WriteUp などが参考になる。
こういった攻撃手法は、SQLに本来の意図ではないSQL命令を紛れ込ませる攻撃ということで、SQLインジェクションという。
SQLインジェクションで発生した有名な事件では、以下のようなものがある。
- Yahoo! BB 顧客情報漏洩事件 – 100億円を超える被害
- PlayStation Network個人情報流出事件
対策としては、ユーザが入力したデータを用いて SQL 命令を実行する場合は、ユーザ入力をSQLとして悪用されないように、シングルクオートなどをエスケープするなどの処理が必要となる。さまざまな手法があるので、SQL無効化の専用関数を用いるべき。
また、データベースシステムは、ネットワーク経由でSQLによる処理を行うが、データベースサーバ自体がインターネットに接続されていて、パスワード攻撃によりデータベース本体に不正アクセスが行われる場合もある。一般的なデータベースを用いたシステムは、フロントエンドのWebサーバ、スレーブDBサーバ、マスタDBサーバの三層構成をとることが多いが、バックエンドのデータベースは、インターネットから隔離しフロントエンドのWebサーバのみ接続できるようにするのが一般的である。
データベースに接続する場合はパスワードにより利用者を限定することができるが、データベースシステム自体がインターネットに接続されていると、パスワード総当たり攻撃(ブルートフォース攻撃)や、パスワードスプレー攻撃(総当たり攻撃は、短時間でパスワード失敗が多発するのでシステムで接続拒否するのが一般的。これを回避するために時間をかけて総当たり攻撃をする手法)により、情報漏洩が発生する。
2分探索木への追加とAVLと2分ヒープ
2分探索木にデータを追加
前回の2分探索木では、基本的な説明ということで、木の生成では直接木構造を生成していた。しかし、本来であれば、扱うデータに応じて木を生成することが求められる。
Javaの場合
以下の Javaのプログラムの関数 entry( … ) では、再帰を使いながら、value を追加した木を返す関数で記述した。
import java.util.*; class BTreeNode { int data ; BTreeNode left ; BTreeNode right ; BTreeNode( int x , BTreeNode l , BTreeNode r ) { this.data = x ; this.left = l ; this.right = r ; } } public class Main { public static void print_tree( BTreeNode p ) { // 木構造をわかりやすくするために()をつけて表示 if ( p != null ) { System.out.print( "(" ) ; print_tree( p.left ) ; System.out.print( p.data ) ; print_tree( p.right ) ; System.out.print( ")" ) ; } } public static BTreeNode entry( BTreeNode p , int value ) { if ( p == null ) { return new BTreeNode( value , null , null ) ; } else { if ( p.data == value ) ; // 何もしない else if ( p.data > value ) p.left = entry( p.left , value ) ; else p.right = entry( p.right , value ) ; return p ; } } public static void main(String[] args) throws Exception { BTreeNode top = null ; top = entry( top , 42 ) ; top = entry( top , 27 ) ; top = entry( top , 72 ) ; top = entry( top , 13 ) ; top = entry( top , 38 ) ; top = entry( top , 64 ) ; top = entry( top , 81 ) ; print_tree( top ) ; System.out.println() ; } } // 動作結果 // (((13)27(38))42((64)72(81)))
- 2分探索木へのデータの追加(Paiza.io)
しかしながらこのプログラムでは、以下のように小さい順序でデータを与えると、右に伸びる木が作られてしまう。
// 不均一な木ができる場合 public static void main(String[] args) throws Exception { BTreeNode top = null ; top = entry( top , 13 ) ; top = entry( top , 27 ) ; top = entry( top , 38 ) ; top = entry( top , 42 ) ; top = entry( top , 64 ) ; top = entry( top , 72 ) ; top = entry( top , 81 ) ; print_tree( top ) ; System.out.println() ; } // 動作結果 // (13(27(38(42(64(72(81)))))))
このような不均一な木が生成されてしまうと、本来であればデータ探索の処理時間は O( log N ) であるが、最悪の状態では O( N ) となってしまう。
AVL木
こういった偏った木が生成されてしまった時には、以下のような処理を加えると、不均一な状態を改善できる。
depth() 関数は与えられた木の深さを求める関数であり、左枝と右枝が大きく違う場所で 木の右回転を行う rotate_right() を実行する。
public class Main { public static int depth( BTreeNode p ) { // 木の深さを求める if ( p == null ) { return 0 ; } else { int l = depth( p.left ) ; int r = depth( p.right ) ; if ( l > r ) return 1 + l ; else return 1 + r ; } } public static BTreeNode rotate_right( BTreeNode p ) { BTreeNode q = p.left ; // p BTreeNode r = q.right ; // / \ q.right = p ; // q p.left = r ; // / \ return q ; // r } public static void main(String[] args) throws Exception { BTreeNode top = null ; top = entry( top , 86 ) ; top = entry( top , 53 ) ; top = entry( top , 92 ) ; top = entry( top , 11 ) ; top = entry( top , 65 ) ; top = entry( top , 22 ) ; // 不均一な状態を確認 print_tree( top ) ; System.out.println() ; System.out.println( "p.left=" + depth( top.left ) ) ; System.out.println( "p.right=" + depth( top.right ) ) ; // top の右回転を実行 top = rotate_right( top ) ; // 改善された状態を確認 print_tree( top ) ; System.out.println() ; System.out.println( "p.left=" + depth( top.left ) ) ; System.out.println( "p.right=" + depth( top.right ) ) ; } }
- AVL木と右回転(Paiza.io)
上記のプログラムでは、左枝が3段で右枝が1段であり不均一な状態となっている。
そこで、最上段の86の左枝を上に移動させるように枝の繋ぎ方を rotate_right() により変更している。実際には、繋ぎ変えを左回転させる処理も作る必要がある。
このように、木の枝の深さが不均一な場所で、このような処理を加えて均一化を図った木構造は AVL 木と呼ぶ。
AVL木は、考案した Velski と Landis の名前が由来(Adelson-Velskii and Landis’ tree)
2分ヒープ(binary heap)
2分探索木では、1つのノードにつき2つのポインタを持ち、データ1件あたりのメモリの使用量が多い。通常の「配列の先頭から昇順にデータを並べる2分探索法」では、途中にデータを挿入する場合、データを後ろにずらす必要があるため、O(N)の処理時間を要する。
これらの問題の解決法の1つとして、2分ヒープがある。左右に均一に成長している2分探索木で、上から番号を以下の様に振ると、i番目のデータの左の枝は 2×i+1 番目、右の枝は 2×i+2 番目であることが判る。
このような順序で配列にデータを保存する方法が2分ヒープである。この方式ならアルゴリズムの説明は省略するが、入れるべきノードの場所でのデータの入れ替えで実現できるため O( log( N ) )で挿入が可能となる。
import java.util.*; public class Main { public static int find_heap( int[] array , int idx , int key ) { while( idx < array.length ) { if ( array[ idx ] == key ) return idx ; // 見つかった else if ( array[ idx ] > key ) idx = idx*2 + 1 ; else idx = idx*2 + 2 ; } return -1 ; // 見つからなかった } public static void main(String[] args) throws Exception { int[] a = { 53 , 11 , 86 , 10 , 22 , 65 , 92 } ; System.out.println( find_heap( a , 0 , 22 ) ) ; } }
- 2分ヒープ(Paiza.io)
レポート課題
以下のようなデータを扱う2分探索木のプログラムを作成せよ。以下の箇条書き番号の中から、(出席番号 % 3+1)のデータについてプログラムを作ること。
- 名前(name)と電話番号(phone)
- 名前(name)と誕生日(year,mon,day)
- 名前(name)とメールアドレス(mail)
プログラムは以下の機能を持つこと。
- 1行1件でデータを入力し、2分木に追加できること。
- 全データを昇順(or降順)で表示できること。
- 検索条件を入力し、目的のデータを探せること。
レポートでは、(a)プログラムリスト,(b)その説明,(c)動作検証結果,(d)考察 を記載すること。
(2年後期実験) 簡単ゲーム作成
1.目的
簡単なゲーム作成を通して、遊びながらプログラミングすることで、スキルアップを目指す。
2.概要
簡単なゲームとして、以下の3つのゲームの中から1つを選び、リンク先の JavaScript による基本機能だけのゲームに、ルール判定・勝敗判定などの機能を組み込む。以下のリンク先の五目並べ、オセロゲームのプログラムは、2人で交互にコマをうつX,Y座標を入力しながら、コマの結果(や勝敗の結果)が表示されるものを作成する。
- 五目並べ(連珠)
- 5つ並べば勝ち…が五目並べ。これだけのルールだと、先手が有利なので先手の黒には、長連、四四、三三など禁じ手などが決められているのが連珠
- オセロゲーム
- 自分のコマで挟んだ相手の駒を裏返しで自分のコマにできる。ベースとなったリバーシを、日本のゲーム研究家の長谷川さんが改良したものがオセロゲーム。ルールが単純だけど、最後に大逆転となることも多い。
- ポーカー
- ポーカーはプレーヤーにトランプのカードを5枚づつ配り、その後にカードを入れ替えた後に、カードの役で勝敗が決まる。カジノゲームとして遊ぶ際には、役の完成度に応じて賭け(ベッド)たり、降りたりしながら遊ぶ。ゲームのやり方により、細かくルールが違ったりローカルルールを設ける場合もある。
プログラム作成の腕試しとしての難易度であれば、ポーカー(すべての役判定をするなら) > オセロ ≒ 連珠 > 五目並べ だと思われる。
もし、この3つのテーマの難易度が面白くないのであれば、三目並べでもよい。ただし人間対コンピュータであること。
3.実験方法
- 概要で示した五目並べ,オセロ,ポーカーのルールについて調べ、自分で2週の実験時間の中で、改良できそうなテーマを最初に1つ選んで下さい。
- 選んだテーマについて、概要で示したリンク先の html ファイルを自分のパソコンに保存し、エディタにてプログラム中の JavaScript の内容を理解してください。
- 以下のチェックシートのファイルをダウンロードし、シートのタブの中から自分が取り組むテーマを選んで、2週の実験の中で実装できそうな機能について、「作成の予定」の列に”〇”を入力してください。
- 実験1週目でプログラムを作成したら、実験時間の最後に、実験1週目で完成した機能について、チェックシートの「1週目」の列に”〇”を入力してください。
- 実験2周目では、前回の続きでプログラムを完成にむけて作成してください。2周目の実験時間の最後に、チェックシートの「2週目・自己判定」の欄に”〇”(一部未完成なら”△”,未完成なら”×”)を入力してください。
- 自己判定の記入が終わったら、同じ実験グループの1人と交代して、相手のプログラムの完成度をチェックし、相手のチェックシートの「2周目・他者判定」の欄に”〇”,”△”,”×”を入力してください。また、他の人のチェックシートに記入する際には、チェックシート表の下の「チェック担当」の欄に名前を記入しておいてください。
4.結果
- 出来上がったプログラムを動かして、作成した機能がちゃんと動作しているということが分かる実行例をいくつか画面のキャプチャ機能を使って「実験結果」として記録してください。
- 実験方法での2週目・他者判定において、(自分では動くと思っていたのに)”△”や”×”の判定のあったものについては、その実行例も「実験結果」として記録してください。
- また、他の人に他者判定をしてもらった自分のチェックシートも「実験結果」として保存してください。
5.レポート
実験レポートでは、以下の内容について記載してください。
- 目的
- 概要
- 自分が作成したテーマのルールなどを簡単にまとめる。
- プログラムの説明
- 自分のプログラムのフローチャートなどを作成し(全体の詳細なフローチャートでなくてもよい)、簡単に全体像を説明してください。
- 自分で工夫した所については、詳しく説明してください。
(例えばオセロなら裏返し処理の詳細なフローチャートなどを記載するなど。)
- 実験結果
- 実験結果として、プログラムリスト、チェックシート、動作の解る実例などをつけてください。
- 実験結果として、プログラムリストや動作例などの図をレポート内に入れる場合は、「プログラムリスト1」「図2動作例」といったように、リスト番号、図番号などをつけてレポート内で引用すること。
- 考察
- 自分で作ったプログラムでうまく動いていない所があれば、その原因やどのようにすれば直せるかを考え、その内容を説明してください。(必ずしもプログラムを直す必要はない)
- 動作で問題が見つからなかった場合は、さらにゲームを完全なものとするにはどういう改良が期待できるか、説明してください。