コンピュータプログラミングII (5)

処理のまとまりをつくる (2)

このスライドの使い方

テキスト等がはみ出した場合は、フォントサイズを小さくして調整する。 「S」 キーで小さく、「B」 キーで大きくなる。また、 下記のキーボード操作が使用できる。(一部キー操作は IE のみ対応)

「←」 or 「Page Up」 前のスライドに戻る
「→」 or 「Page Down」 or「スペース」 次のスライドに進む
「Home」 and 「End」 先頭(Home)または最後(End)のスライドへ移動
「C」 or 「contents?」 をクリック スライド一覧の表示
「F11」 or 「Ctrl+Shift+F (Win)」, 「Cmd+Shift+F (Mac)」 フルスクリーン表示と通常表示の切り替え
「F」 フッタの表示と非表示の切り替え
「A」 全スライド表示に切替 (印刷時に使う)
「S」「-」/「B」「+」 フォントサイズの大(B) 小(S)

本日のゴール

復習: 引数のある関数の作り方

void 関数名(引数1の型 引数1の変数名, 引数2の型 引数2の変数名, ...) {
  // ここに処理を書く

}

void drawHuman(int x, int y) {
  // ここに処理を書く

}

復習: 関数の呼び出し

いずれにしても、関数を定義する順番は、実行される順番と無関係

void setup() {

}

void draw() {

}

void drawHuman(int x, int y) {

}
void drawHuman(int x, int y) {

}

void draw() {

}

void setup() {

}

これまでと逆順に書いても同じこと。

復習: 引数の受け渡し

void setup() {
  int a = 3, b = 2;
  drawHuman(a, b);
}

void drawHuman(int x, int y) {
  // x = a、y = b が行われたのと同じ状態でスタート

}

引数の受け渡し: 配列の場合

void setup() {
  int[] p = new int[] {3, 2};
  drawHuman(p);
}

void drawHuman(int[] c) {
  // int[] c = p が行われたのと同じ状態でスタート

}

引数の受け渡し: 配列の場合

練習: 人の列が2列

第4回で定義したdrawHuman()を改良し、人の列の x座標、y 座標と、 「人々の胴体の長さを要素とする配列」を引数とする人々を描く関数 drawHumans(int x, int y, int[] bodyHeight) を定義し、 size(400, 650)の画面内に、y = 300 と y = 600 の2列に人々を描こう。 なお、drawHumans() の中で左から順番に固定の異なる色を指定するため、 fill((i * 50) % 360, 100, 100) などとせよ。

練習: 人の列が2列

final int x0 = 50;                // 左端の人の中心の x座標
final int y0 = 300;               // 人の足元の y座標の初期値
final int dx = 50;                // 人の中心線間の距離
final int dy = 300;               // 上の段と下の段との距離
final int humanBodyWidth = 20;    // 体の幅
final int faceDiameter = 30;      // 顔の直径
final int[] humanBodyHeight = new int[] { 95, 136, 126, 118, 136, 106, 122 };  // 胴体の長さ; 人数は7名
  
void setup() {
  size(400, 650);                  // ウィンドウのサイズ
  background(255, 255, 255);       // 背景色(白)
  noStroke();                      // 輪郭線を無効に
  colorMode(HSB, 360, 100, 100);   // カラーモードをHSBに
  int x = x0;
  int y = y0;

  drawHumans(x, y, humanBodyHeight);                   // 人々の形を描く
  y = y + dy;                                          // 下の段に移動する
  drawHumans(x, y, humanBodyHeight);                   // 人々の形を描く
}

練習: 人の列が2列

/**
 *  人々の形を描く
 *  @param x 人の列の左端のx座標
 *  @param y 人の列のy座標
 *  @param bodyHeight 人の胴体の長さの配列
 */
void drawHumans(int x, int y, int[] bodyHeight) {

  // ここを考える

}

値を返す関数

値を返す組み込み関数

関数の戻り値は 1つの値として式内で用いることができる。

x = sin(x) + c;
a = int(random(0, 360)) / 60;

値を返す独自の関数

x = sin(x) + myFunction(a, b);

値を返す関数のつくりかた

戻り値の型 関数名(引数1の型 引数1の変数名, 引数2の型 引数2の変数名, ...) {
  // ここに処理を書く

  return 返す値;
}
int myFunction(int x, int y) {
  return x + y;
}

値を返す独自の関数 - 例1

2つのfloat型の実数の平均値を求める average()

/**
 * 2つの実数の平均値を求める
 * @param a 実数
 * @param b 実数
 * @return 実数aと実数bの平均値
 */
float average(float a, float b) {
  float result;
  result = (a + b) / 2;
  return result;
}

平均値は実数の値になるので、戻り値の型を float としている。次のように return 文だけでもよい。

float average(float a, float b) {
  return (a + b) / 2;
}

値を返す独自の関数 - 例2

int型の配列の全要素の中から最大の値を求める maximum()

/**
 * 配列の要素の最大値を求める
 * @param a 配列
 * @return 配列aの要素の最大値
 */
int maximum(int[] a) {
  int result = a[0];
  for(int i = 1; i < a.length; i++) {
    if(a[i] > result) {
      result = a[i];
    }
  }
  return result;
}

引数の参照先の配列の長さは任意。

練習: 値を返す独自の関数

int型の配列の全要素の和を求める sum()

void setup() {
  int[] a = new int[] {
    3, 5, 7, 8, 9, 10, 2, 4, 1, 6
  };
  // ここで関数 sum を呼び出し、和を println でコンソールに表示
  ...
}

/**
 * 配列の要素の総和を求める関数 sum の定義
 */
...

例題: 棒グラフの最大値に線を引く

サイコロの目の棒グラフで、頻度の最大値に線を引く

例題: 棒グラフの最大値に線を引く

前準備

final int fps = 24;          // フレーム数/秒  (1フレームごとにサイコロを振る前提)
final int dY = 60;           // 行の間隔
final int dH = 20;           // 度数1当りの棒グラフの長さ
final int w = 40;            // 棒の幅
final int m = 6;             // サイコロの目の最大値
final int marginTop = 50;    // 上の余白
final int marginLeft = 50;   // 左の余白 (文字を除く)
final int marginChar = 15;   // 文字の左余白
int[] freq;            // サイコロの目ごとの回数を格納

void setup () {
  size(640, 400);
  frameRate(fps);
  noStroke();
  background(255, 255, 255);
  colorMode(HSB, 360, 100, 100);
  freq = new int[m];
}

例題: 棒グラフの最大値に線を引く

void draw() {
  background(0, 0, 100);
  freq[(int)random(0, m)]++;          // 0-(m-1) の乱数を添字とする(小数点以下切り捨て)
  for(int i = 0; i < freq.length; i++) {
    int y = marginTop + i * dY;       // 棒の中心のy座標
    int h = freq[i] * dH;             // 棒の高さ(x方向)
    if(marginLeft + h + dH > width) {    // 次が書けないほど右に来ているか? (width はウィンドウの幅)
      fill(0, 0, 0);                  // 色を黒に変更
      noLoop();                       // 次から draw() を呼び出さない
    }
    else {
      fill(360.0 * i / m, 100, 100);  // 棒ごとに色相を変更
    }
    rect(marginLeft, y - w / 2, h, w);
    fill(0, 0, 0);                    // 文字の色は黒
    textSize(16);
    text(i + 1, marginChar, y);       // 棒の左に i + 1 を表示
  }
  // 最大値を表す縦棒を描く
  rect(marginLeft + maximum(freq) * dH, 0, 3, height);
}

関数 maximum() に、配列 int[] freq を渡している。

値を返す独自の関数 - 例3

配列のn番目以降の要素の最大値の位置を返す

/**
 * 配列のn番目以降の要素の最大値の位置を返す
 * @param a 配列
 * @param n 探索開始位置
 * @return 配列の要素の最大値の位置
 */
int searchMaximum(int[] a, int n) {
  int max = a[n];                             // 最も高い値
  int index = n;                              // 最も高い値の位置
  for(int i = n + 1; i < a.length; i++) {
    if(a[i] > max) {
      max = a[i];
      index = i;
    }
  }
  return index;
}

例題: 最も背の高い人を先頭に

あらかじめ指定された身長の 7人の人物を並べて表示する。 左端(先頭)に最も背の高い人が来るように順番を入れ替えてから表示しなさい。 左端以外の順序は問わない。

最も背の高い人が複数いる場合には、そのうちの1人が左端に来ればよい。

ヒント: 描画の前に searchMaximum を使って最も背の高い人が何番目にいるかを調べ、その人と左端の人とを入れ替えるとよい。

例題: 最も背の高い人を先頭に (穴埋め)

final int x0 = 50;                          // 人の中心の x座標の初期値
final int y = 300;                          // 人の足元の y座標
final int dx = 50;                          // 人の中心線間の距離
final int humanBodyWidth = 20;              // 体の幅
final int faceDiameter = 30;                // 顔の直径
final int[] humanBodyHeight = new int[] {   // 胴体の長さ; 人数は7名
  145, 186, 176, 168, 186, 156, 172
};

void setup() {
  size(400, 400);             // ウィンドウのサイズ
  background(255, 255, 255);  // 背景色(白)
  noStroke();                 // 輪郭線を無効に

  // 最も背が高い人の位置を求める

  // その人と先頭の人の胴体の長さを入れ替える

  // 人の列を描く

  }
}

例題: 最も背の高い人を先頭に (解答)

void setup() {
  size(400, 400);             // ウィンドウのサイズ
  background(255, 255, 255);  // 背景色(白)
  noStroke();                 // 輪郭線を無効に

  // 最も背が高い人の位置を求める
  int index = searchMaximum(humanBodyHeight, 0);
  // その人と先頭の人の胴体の長さを入れ替える
  int temp = humanBodyHeight[0];
  humanBodyHeight[0] = humanBodyHeight[index];
  humanBodyHeight[index] = temp;

  // 人の列を描く
  fill(0, 0, 0);                                                         // 塗る色 (黒)
  int x = x0;
  for (int i = 0; i < humanBodyHeight.length; i++) {
    ellipse(x, y - humanBodyHeight[i], faceDiameter, faceDiameter);      // 頭
    rect(x - humanBodyWidth / 2, y - humanBodyHeight[i], 
         humanBodyWidth, humanBodyHeight[i]);                            // 胴体
    x = x + dx;
  }
}

単純選択ソート (演習問題 問題4,5)

人を背の高い順に並べることを考える。

「例題: 最も背の高い人を先頭に」の方法でまず左端の人を確定させ、 残りの人たちについても同じ方法で(残りの範囲の)左端の人を確定させ、と繰り返す。 このアルゴリズムを単純選択ソートと呼ぶ。

この方針で、演習問題の問題4,5 を解くこと。

戻り値の利用

戻り値を使う使い方

x = sin(x) + myFunction(a, b);   // 戻り値は式の一部として使われる

戻り値を捨てる使い方

myFunction(a, b);                // 戻り値は捨てられる

return 文の位置

int maximum(int x, int y) {
  if(x > y) {
    return x;
  }
  else {
    return y;
  }
}

値を返す関数では、上の例のように分岐があっても、必ず return 文で値を返して終了するするようになっていなければならない。

int maximum(int x, int y) {
  if(x > y) {
    return x;
  }
  // これでは、x > y が false のときに値を返さずに終わってしまうためエラー
}

戻り値のない関数での return 文

戻り値のない関数でも、値を取らない return 文により処理を途中で終了させることができる。

void kimagure() {
  if(ramdon(0, 1) > 0.5) {
    return;
  }
  // 1/2 の確率で実行
  ...
}