オブジェクト・プログラミング

オブジェクト・プログラミング
第6回
効率
品質の高いプログラム(復習)

品質の高いプログラムって?





正しさ
使いやすさ
再利用性
可読性(読みやすさ)
効率(速さ)
今日の目標

ソフトウエアの効率について説明できるようにな
る。



効率の悪いプログラムを使うとどうなるか説明できる
ようになる。
効率を調べるための指標(BigO記法)を説明できるよ
うになる。
メソッドの返り値について説明できるようになる。

メソッドの返り値を使ったプログラムが書けるようにな
る。
効率が悪いとどうなるか?



1万人の社員がいる会社があります。
ログインするために社員番号でパスワード
を検索します。
1人の検索に0.1秒かかると、全員がログ
インし終わるまでに何分かかりますか?
こたえ:
1分で600人だから
10000/600=16.66分
→大変だ!
前回のプログラムを復習

配布資料(解答例)を参照
ItemInfoFolder
VendingMachineMain
main(args : String[])
取扱商品情報の操作の
順番に責任を持つ
insert(insertInfo : Iteminfo)
delete(deleteKey : int)
search(searchKey : int)
display()
取扱商品情報を
管理する責任を持つ
1
0..n
ItemInfo
no : int
name : String
取扱商品情報の保持に
責任を持つ
今日の問題

今日はたくさんの商品情報を取り扱います。



1万個の商品情報
10万個の商品情報
現実の自動販売機ではありえませんが、コ
ンピュータの自動販売機ならできますね。

例えば、WEBでソフトやゲームを販売する場
合。
たくさんの商品情報を取り扱う

ItemInfoFolderクラスの変更



配列の大きさを増やす
forループの回数を増やす
VendingMachineMainクラスの変更

forループを使って、たくさんの商品情報を挿
入する。
ItemInfoFolderクラスの変更

配列の大きさを一元管理できるように変数を用
意する。(10万まで入るようにする)
int arrayMax = 100000;

配列の大きさを増やす
ItemInfo[] itemInfoArray = new ItemInfo[arrayMax];

検索、削除などに使っているforループの回数を
増やす
for(int i=0;i<arrayMax;i++){
}
VendingMachineMainクラスの
変更

forループを使って、たくさんの商品情報を
挿入する。10000と100000でやってみます。
//メインメソッド
public static void main(String[] args) {
ItemInfoFolder folder = new ItemInfoFolder();//商品情報を
管理するオブジェクトを作る
int registerNum = 10000;
//取扱商品を10000種類登録する
for(int i=0;i<registerNum;i++){
folder.insert(new ItemInfo(i, “コーラ”+i));//今日の商品名は
仮にコーラ+商品番号
ex)コーラ2030
}
System.out.println("挿入終了");
}
このプログラム、動きます?



10000個挿入はそれなりの早さでできます。
(Pentium800MHzで約0.5秒)
ですが、100000個挿入にするとなかなか
“挿入終了”と出てきません。やたら時間が
かかります。
何故でしょう?
insertのif文


問題:insertのif文は何回実行されているでしょ
うか?
10000回の時と100000回の時でどうでしょう?
public void insert(ItemInfo insertItem){
for(int i=0;i<arrayMax;i++){
if(itemInfoArray[i] == null){
itemInfoArray[i] = insertItem;
break;
}
}
}
計算方法





1つ挿入の時→1回
2つ挿入の時→1+2回=3回
3つ挿入の時→1+2+3回=6回
100個挿入の時→1+2+…..+100回
=(1+100)×50 = 5050回
N個挿入の時→(1+n)×n/2回
=約n^2/2回
答え


10000個のとき
約10000×10000 / 2
= 50,000,000回
(5千万回)
100000個のとき
約100000×100000 / 2
= 5,000,000,000回
(50億回)
約0.5秒
おそらく50秒
挿入を早くする方法

挿入する番地を覚えておけばよい。

属性にsizeを加えます。
//ItemInfoFolderクラス
public class ItemInfoFolder {
ItemInfo[] itemInfoArray = new ItemInfo[arrayMax];//商品
情報を管理するための配列を作る
int size = 0;
…
}
効率のよい挿入のプログラム
//ItemInfoFolderクラス
public class ItemInfoFolder {
ItemInfo[] itemInfoArray = new ItemInfo[arrayMax];
int size = 0;
public void insert(ItemInfo insertItem){
itemInfoArray[size] = insertItem;
size++;//一つ挿入したら番地を増やす
}
…
}
削除のプログラムも変更する
//ItemInfoFolderクラス
public class ItemInfoFolder {
ItemInfo[] itemInfoArray = new ItemInfo[arrayMax];
int size = 0;
public void delete(int deleteKey){
…(削除のプログラムを書く)
size--;//一つ削除したら番地をへらす
}
…
}
検索、削除のプログラムも少し
効率化する。
nullかどうか
調べる必要なくなる
//取扱商品情報を検索するメソッド(size導入前)
public void search(int num){
for(int i=0;i<arrayMax;i++){
if(itemInfoArray[i] != null){
if(itemInfoArray[i].no == num){
//System.out.println("見つかりました。");
return;
}
}
}
//System.out.println("見つかりませんでした。");
}
配列全部を検索
//取扱商品情報を検索するメソッド(size導入後)
public void search(int num){
for(int i=0;i<size;i++){
if(itemInfoArray[i].no == num){
//System.out.println("見つかりました。");
return;
}
}
}
//System.out.println("見つかりませんでした。");
入っている分だけ検索
時間を計る方法

StopWatchクラスの作成
StopWatchクラス
public class StopWatch {
long startTime;
long stopTime;
public void start(){
startTime = System.currentTimeMillis();
}
public void stop(){
stopTime = System.currentTimeMillis();
}
}
public long getTime(){
long time = stopTime – startTime;
return time;
}
コメントは省略
StopWatchクラス解説(1)

long型

int型と同じで整数が入る変数ですが、int型よ
りも、多くの数を表わすことができます。



int型----- -2147483648~2147483647
long型--- -9223372036854775808~
9223372036854775807
今回は時間をlong型であらわします。

単位はミリ秒単位。(1/1000秒単位)
StopWatchクラス解説(2)

start()メソッド
public void start(){
startTime = System.currentTimeMillis();
}
System.curretnTimeMills()メソッドは、現在の時刻をlong型で
返します。
( 1970 年 1 月 1 日午前 0 時 0秒 を0とした差が返ってきます。)
startTime変数に格納します。stop()メソッドも同様です。
StopWatchクラス解説(3)

getTime()メソッド
public long getTime(){
long time = stopTime – startTime;//①
return time;//②
}
①では、start()メソッドが呼ばれた時刻とstop()メソッドが呼ばれた
時刻を引き算した結果(要するにかかった時間)をtime変数に格納
します。
②では、①で格納した時間を返り値として返します。
次で説明
メソッドの返り値
今までは、
今日は
ここまで。
返り値
のお話
仕事してくれよ!+引数
メソッドA()
呼び出し側
終わったぜ!+返り値
getTime()メソッドの返り値
かかった時間を計算して!+(引数なし)
呼び出し側
getTime()
計算終わったよ!+1203ミリ秒かかったよ
返り値のあるメソッドの書き方
①
public long getTime(){
long time = stopTime – startTime;
return time;
}
②
①何も返さない場合、voidと宣言していたところに戻す返り値の型
名を書きます。
②値を返すときは、return [変数名] で返します。
そこでメソッドは終了します。
StopWatchクラスの利用法
//メインメソッド
public static void main(String[] args) {
ItemInfoFolder folder = new ItemInfoFolder();
StopWatch sw = new StopWatch();//StopWatchをインスタンス化
sw.start();//ストップウオッチをスタートさせる。
//10万個の商品情報を登録
int registerNum = 100000;
for(int i = 0;i<registerNum;i++){
folder.insert(new ItemInfo(i, “コーラ”+i));
}
sw.stop();//ストップウオッチを止める。
long time = sw.getTime();//かかった時間を返り値として受け取り、変数に格納
System.out.println(“かかった時間は”+time+”ミリ秒です”);//かかった時間を表示
}
検索の効率

問題:1万個のデータを1万回検索します。
//メインメソッド
//一万個のデータを一万回検索する
public static void main(String[] args) {
ItemInfoFolder folder = new ItemInfoFolder();//商品情報を
管理するオブジェクトを作る
//取扱商品を10000種類登録する
//省略
}
//今登録した取扱商品をすべて検索する
for(int i=0;i<10000;i++){
folder.search(i);
}
1万行の出力!


1万回検索すると、1万行「見つかりまし
た」が出力されてしまいます。これは分かり
ずらいし、速度も遅くなってしまうので、
searchメソッドを修正します。
searchメソッドにboolean型の返り値を加え
ます。


見つかったら,trueを返します。
見つからなかったら,falseを返します。
searchメソッドの修正
返り値を返すことを宣言します。
//取扱商品情報を検索するメソッド(ItemInfoFolderクラス)
public boolean search(int searchKey){
for(int i=0;i<size;i++){
if(itemInfoArray[i].no == searchKey){
return true;
}
見つかったら、trueを
}
返します。
return false;
この時点で、このメソッドは
}
終了します。
見つからなかったら、
falseを返します。
searchメソッド呼び出しの修正
//メインメソッド
//一万個のデータを一万回検索する
public static void main(String[] args) {
ItemInfoFolder folder = new ItemInfoFolder();//商品情報を
管理するオブジェクトを作る
int registerNum = 10000;
//取扱商品を10000種類登録する
//省略
}
//今登録した取扱商品をすべて検索する
for(int i=0;i<registerNum;i++){
boolean result = folder.search(i);//検索し結果を格納
if(result == false){
System.out.println(“見つかりませんでした”);
}
必ず見つかるはずなので、
}
この行は表示されない。
実験環境は整った



さて、実験してみましょう。
Pentium800MHzでは1万個の検索に800
ミリ秒(0.8秒)かかりました。
10万個ではどれぐらいかかるでしょう?
(実験しないほうが賢明)
if文の実行回数を調べる

例によってif文の実行回数を調べます。
//取扱商品情報を検索するメソッド(ItemInfoFolderクラス)
public boolean search(int searchKey){
for(int i=0;i<size;i++){
if(itemInfoArray[i].no == searchKey){
return true;
}
}
}
return false;
}
最初と最後で違う!


0を検索した場合→1回のif文で見つかる。
9999を検索した場合→1万回のif文で見つ
かる。
平均5000回のif文で見つかる。
よって、if文の回数は5000×10000=50,000,000
(5千万回)
よって、n個の検索で必要なif文の数は
n/2 × n = n^2/2
10万回検索をしたら、


10万要素を10万回検索した場合は、
100000×100000 /2 = 5,000,000,000
(50億回)
の比較が必要です。
理論上は50秒で検索が終わります。
ですが、実際にやってみると、7分ぐらいか
かります。これはオブジェクトにアクセスの
オーバーヘッドが大きくなるためです。
検索の効率を考える

前回までの方法を「リニアサーチ」といいま
す。
9を探す場合
1
2
3
4
5
6
7
8
9
[0]
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
10
11
[9] [10]
あった!
検索の効率を上げる方法

バイナリサーチ!

配列の中身がソート済み(順番に並んでいる)
の場合に有効。

今日はfor文で入れているのでソート済みになって
います。
バイナリサーチの考え方(1)
①まず、真ん中を探します。
②違った場合も、探す範囲は半分に絞られます。
(数が少なかったら右半分、逆なら左半分にあります。)
9を探す場合
1
2
3
4
5
6
7
8
9
[0]
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
ちがった!
10
11
[9] [10]
9は6より大きいから、
この範囲に絞られる
バイナリサーチの考え方(2)
③次に、その範囲の中のまた真ん中を探します。
④それを見つかるまで繰り返します。
9を探す場合
1
2
3
4
5
6
7
8
9
[0]
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
ちがった!
10
11
[9] [10]
9は8より大きいから、
この範囲に絞られる
バイナリサーチの実装
//バイナリサーチをするメソッド(ItemInfoFolderクラスに追加する)
public boolean binarySearch(int searchKey){
int lower = 0;//探す配列の範囲の最小値
int upper = size-1;//探す配列の範囲の最大値
int center;//次に調べるべき配列の番地
while(true){
center = (lower + upper)/2;//次に調べるのを範囲の真中に設定
if(itemInfoArray[center].no == searchKey){
return true;//見つかった
}else if(lower > upper){
return false;//見つからず、かつ、もう調べる範囲がなくなった。
}else{
//見つからないので、次に調べる範囲を狭める
if(itemInfoArray[center].no < searchKey){
lower = center +1;//範囲を右半分にする
}else{
upper = center -1;//範囲を左半分にする
}
}
}
}
バイナリサーチの効率(1)

例えば、100個の要素の中から検索するこ
とを考えてみましょう。
リニアサーチの場合
比較の回数は
-最小の場合で1回
-最大の場合で100回
-平均50回
バイナリサーチの場合
比較の回数は
-最小の場合で1回
-最大の場合で7回
-平均7回以下
バイナリサーチの効率(2)
要素数
比較最大回数
リニアサーチ
バイナリサーチ
10
10
2
100
100
7
1000
1000
10
10000
10000
14
100000
100000
17
1000000
1000000
20
10000000
10000000
24
バイナリサーチの効率(3)
バイナリサーチの
比較回数
検索できる範囲
0
1
1
2
2
4
3
8
4
16
5
32
6
64
7
128
2^比較回数
ようするに
「2の巾乗」
が検索できる範囲
になる。
2の巾乗を逆計算する

問題:100をバイナリサーチすると、最大何
回の比較が必要か。計算で求めよ
答え log2(100) = 6.644
切り上げて7回
バイナリサーチの効率(4)

Nつの要素の中から検索するときの効率
は、以下のように計算されます。
リニアサーチの場合
比較の回数は
-最小の場合で1回
-最大の場合でN回
-平均N/2回
Nに比例
バイナリサーチの場合
比較の回数は
-最小の場合で1回
-最大の場合でlog2(N)回
-平均log2(N)回以下
Log2(N)
に比例
効率を比べる一般的な記法

BigO記法

BigO記法は、その名の通り、大文字のOを使
いますが、order(次数)の意味です。
bigO記法
Nに比例
O(N)
Log2(N)に比例
O(LogN)
Nの2乗に比例
O(N^2)
BigOのグラフ化
40
35
30
O(N)
O(N^2)
O(logN)
O(1)
25
20
15
10
5
0
1
2
3
4
5
6
7
8
9 10 11 12 13
課題

実行するプログラムを配りますが、それに
は、コメントが一つも書いてありません。


適切なコメントをつけて提出しなさい。
プログラムを動かして、リニアサーチとバイ
ナリサーチの速さを比べてみなさい。Nの
数は最低1000,10000,20000,30000の4
種類で比べなさい。

結果と考察を提出しなさい。
提出方法



[email protected]宛て。
今回はソースのほかに、
実行結果、考察を書いて送ってください。
Subjectはログイン名を必ず書いてください。



ex) t96844ym
余計な[]などをつけないでください。
水曜まで。