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

復習

このスライドの使い方

テキスト等がはみ出した場合は、フォントサイズを小さくして調整する。 「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)

本日のゴール

お知らせ: 中間試験

持ち込み不可なので、紙上に文法間違いなく記述できるように練習しておくこと。

復習: Processing 開発環境(PDE)のショートカットキー

特に使用頻度の高いものを挙げる。

ファイルの保存 (Save)
Ctrl-S
プログラムの実行 (Run)
Ctrl-R
書式の自動調整(字下げなど) (Auto Format)
Ctrl-T

その他はリファレンスやメニューの記述を参照のこと。 なお、リファレンスに記載はないが、Ctrl+矢印キー なども存在する。

まとめ: 引数と戻り値のある関数の作り方

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

  return 式;
}

戻り値の型、関数名、引数の数、各引数の型と変数名、は好きに決めてよい。

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;
}

まとめ: 引数

まとめ: 戻り値

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

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

復習: コンソールへの値の表示

利用例

int x;
int y = 6;
int z = 4;

x = y + z * 3;
println("x: " + x);
x = (y + z) * 3;
println("x: " + x);

文字列と文字列、または文字列と数値を + で演算すると、数値は文字列に変換され文字列の連結となる。 また、演算は左から順に演算される(左結合)。

"x: " + 10 + 6 → ("x: " + 10) + 6 → ("x: " + "10") + 6
 → "x: 10" + 6 → "x: 10" + "6""x: 106"

練習: 関数の復習ドリル

以下のすべての問題の出力を行う関数を作り、 setup()関数の中から呼び出しテストしなさい。

各問題が終了してから次の問題に進むこと。 わからない問題があった場合は、手を挙げて副手を呼び、 どこまでわかっていてどこからわかっていないかを明確に発言して、 わからない点について尋ねること。

(1)関数名:printHello()
機能: Hello Worldとコンソールに表示する。
(2)関数名:printHello2()
機能: (1)で作ったprintHello()を2回呼び出す。
(3)関数名:printHellos(int n)
機能: (1)で作ったprintHello()をn回呼び出す。
テスト:
n=10回としてテストすること。

練習: 関数の復習ドリル

(4)関数名:void calc1(int x)
機能: xが10未満ならsmall numberと表示する。
テスト:
calc1(8)、calc1(10) をそれぞれ実行してテストすること。
(5)関数名:int calc2(int x)
機能: xを2倍してreturnする。
テスト:
setup()関数内部でprintln("calc2="+calc2(5))として確認すること。
(6)関数名:float calc3(float x)
機能: xを2倍してreturnする。
テスト:
setup()関数内部でcalc3(5)とcalc3(1)の合計値を出力せよ。

練習: 関数の復習ドリル

(7)関数名:int calc4(int x,int y)
機能: xとyの合計の2倍の値をreturnする。
テスト:
setup()関数内部でcalc4(5,1)の値を出力せよ。
(8)関数名:int calc5(int x,int y,int z)
機能: xとyとzの合計の2倍の値をreturnする。
テスト:
setup()関数内部でcalc5(5,1,4)値を出力せよ。
(9)関数名:int sum(int n)
機能: 1からnまでの合計の値をreturnする。
テスト:
setup()関数内部でsum(10)の値を出力せよ。

練習: 関数の復習ドリル

(10)関数名:int sum(int p,int q)
機能: pからqまでの合計の値をreturnする。
テスト:
setup()関数内部でsum(1,100)の値を出力せよ。
(11)関数名:int sum(int[] a, int index)
機能: intの配列a[]を引数として、その配列のindex番目以降の要素の合計値をreturnする。 ただし、index の値が不正の場合には Index Error! とコンソールに表示し -1 をreturnする。
テスト:
int[] a={1,2,3,4,5,6,7,8,9,10};
以下を出力せよ。
sum(a,0);
sum(a,9);
sum(a,10);

練習: 関数の復習ドリル

(12)関数名:int selectMaxValue(int[] a)
機能: 配列aの中の最大の値をreturnする。
テスト:
int[] a={10,9,8,4,15,0,-3,18,9,7}
selectMaxValue(a)を出力せよ。
(13)関数名:int selectMaxIndex(int[] a)
機能: 配列aの中で最大の値が入っている要素の位置(配列のindex)をreturnする。 2か所以上に最大値がある場合は、indexの小さい方をreturnする。
テスト:
int[] a={10,9,8,4,15,0,-3,18,9,7}
selectMaxIndex(a)を出力せよ。

練習: 関数の復習ドリル

(14)関数名:int selectMinIndex(int[] p)
機能: 配列pの中で最小の値が入っている要素の位置(配列のindex)をreturnする。 2か所以上に最小値がある場合は、indexの小さい方をreturnする。
テスト:
int[] p={10,9,8,4,15,0,-3,18,9,7}
selectMinIndex(p)を出力せよ。
(15)関数名:int selectDiffMaxMinValue(int[] p)
機能: 配列pの中から最大と最小の差分の値をreturnするプログラムを作成せよ。(13),(14)を利用せよ。
テスト:
int[] p={10,9,8,4,15,0,-3,18,9,7}
selectDiffMaxMinValue(p)を出力せよ。

練習: 関数の復習ドリル

(16)関数名:void swapArrayElements(int[] p, int i,int j)
機能: 配列pのi番目とj番目を入れかえるプログラムを作成せよ。
テスト:
int[] p={10,9,8,4,15,0,-3,18,9,7}
swapArrayElements(p,0,9)をsetup()関数内部で実行し前後の配列内容すべて表示して交換されたことを確認せよ。
(17)関数名:int selectMaxIndex(int[] a,int i,int j)
機能: 配列aのi番目からj番目の間で最大の値が入っている要素の番号(配列index) (最大値が複数回見つかる場合は最初の要素の番号)をreturnする。
テスト:
int[] a={10,9,8,4,15,0,-3,18,9,7}
selectMaxIndex(a,1,6)を出力せよ。
(18)関数名:boolean swapTwoArrays(int[] a, int[] b)
機能: 二つの長さが同じ配列を引数として中身を入れ替える関数を作成せよ。 長さが違う場合にはfalseをreturnすること、入れ替えが成功した場合にはtrueをreturnする。
テスト:
int[] a ={1,2,3,4};
int[] b ={4,3,2,1};
として関数呼び出し前、呼出し後に出力して確認せよ。

復習: さまざまなアニメーション: 重力

プログラミング入門の第3週「さまざまなアニメーション: 重力」において、 円をボールに見立てて、重力によって落下するアニメーションがあった。 これを思い出してから今日の演習問題に取り組もう。

復習: さまざまなアニメーション: 重力

// 定数
final float gravity = 9.8 / 60;  // <-- 1/60秒あたりの重力加速度
// 変数
float y;                         // <-- ボールのy座標
float vy;                        // <-- ボールの速度のy成分

void setup() {
  size(200, 480);
  noStroke();
  fill(0, 0, 0);
  y = 0;
  vy = 0;
}

void draw() {
  vy += gravity;    // <-- 速度に重力加速度を加算する
  y += vy;          // <-- ボールが速度ぶん移動する

  background(255, 255, 255);
  ellipse(100, y, 40, 40);
}

変数名を一部変更し(speed -> vy)、重力加速度を定数にしている。

復習: 定数と修飾子

float x;
final float PI = 3.14;      // 定数

[参考] Java では定数を表すときに final と static を併用するが、これは2年生で扱う。

復習: 配列の index と定数

配列は同じ型の値をまとめ、添え字(index)がその順序を表すが、 要素ごとに意味づけがされていることがある。

float[] ball = new float[3];

ball[0] = 1.0;      // 0 は X座標
ball[1] = 5.5;      // 1 は Y座標
ball[2] = 180.0;    // 2 は 色相(Hue)

番号ではわかりにくいため、わかりやすい名前の定数を用意するとよい。

final int X = 0;    // 0 は X座標
final int Y = 1;    // 1 は Y座標
final int HUE = 2;  // 2 は 色相(Hue)

float[] ball = new float[3];

ball[X] = 1.0;
ball[Y] = 5.5;
ball[HUE] = 180.0;

復習: 演算により変数の値を変化させる

変数の値に対して何らかの演算を行い、その演算結果を元の変数に代入したいことがある。

x = x + 1;
x = x - 1;
x = x + a;
x = x / (b + c);

これらは、インクリメント演算子(++)、デクリメント演算子(--)、複合代入演算子(+=, -=, *=, /=, %= など)を用いて書くことができる。

x++;              // x = x + 1;
x--;              // x = x - 1;
x += a;           // x = x + a;
x /= b + c;       // x = x / (b + c);

インクリメント/デクリメント演算子のあれこれ

インクリメント演算子(++)、およびデクリメント演算子(--)には前置と後置があり、演算のタイミングが異なる。

y = x++;          // 代入をしてからインクリメント
y = ++x;          // インクリメントしてから代入

y = ++x; などと書かずに x++; y = x; と分けて書くのがわかりやすい。

間違いやすい例:

int x = 1;
if(x++ == 1) {
  println(x++);
}

2 と表示される。

再帰呼び出し

例: 小さくなっていく正方形の列

例: 小さくなっていく正方形の列 (再帰呼び出し)

float x0 = 50.0;           // 元の正方形の中心のx座標
float y0 = 200.0;          // 元の正方形の中心のy座標
float m0 = 100.0;          // 元の正方形の一辺の長さ
float r = 0.75;            // 縮小比率

void setup() {
  size(400, 400);
  stroke(0, 0, 0);
  background(255, 255, 255);
  noFill();
  drawManySquares(x0, y0, m0);
}

例: 小さくなっていく正方形の列 (再帰呼び出し)

/**
 *  正方形をたくさん描く
 *  @param x 正方形の中心のx座標
 *  @param y 正方形の中心のy座標
 *  @param m 正方形の1辺の長さ
 */
void drawManySquares(float x, float y, float m) {
   if (x + m / 2 < width) {   // 右端にはみ出さないなら描く
     // 最初の正方形は自分で描く
     rectMode(CENTER);
     rect(x, y, m, m);
     // その右に続く正方形の列は自分の分身に描かせる
     float nextM = m * r;
     float nextX = x + m / 2 + nextM / 2;
     drawManySquares(nextX, y, nextM);  // 自分を呼び出す
   }
}

復習: rectMode(CENTER) とすると、左上ではなく中心の座標を指定して描画するモードになる。

練習: 肩車

大男の横に、胴体の長さが半分の2人を縦に並べる(肩車)。 この半分の人の横にそれぞれ半分の2人を縦に並べる。 胴体の長さがある程度(例えば、3頭身)以下の場合はそれ以上は横に並べない。 これを再帰で描こう。 size(400,600)の画面内の左端に、胴体の長さ 500程度の大男から始めよう。

練習: 肩車

ヒント: 人を描くのは胴体の長さ bodyHeight と x座標、y座標を引数とする再帰関数 drawSmallerHumans()を定義する。 胴体の長さ bodyHeight がある値を超えていなければ、次を描かずに関数を終える。

final int x0 = 50;                       // 人の中心の x座標の初期値
final int y0 = 550;                      // 人の足元の y座標
final int dx = 50;                       // 人の中心線間の距離
final int humanBodyWidth = 20;           // 体の幅
final int faceDiameter = 30;             // 顔の直径
final int tallestBodyHeight = 500;       // 大男の胴体の長さ

void setup() {
  size(400, 600);                             // ウィンドウのサイズ
  background(255, 255, 255);                  // 背景色(白)
  noStroke();                                 // 輪郭線を無効に
  colorMode(HSB, 360, 100, 100);              // カラーモードをHSBに
  drawSmallerHumans(tallestBodyHeight, x0, y0);  // 人々の形を描く
}

練習: 肩車

/**
 *  人々の形を描く
 *  @param bodyHeight 人の胴体の長さ
 *  @param x 人の横位置
 *  @param y 足元の座標
 */
void drawSmallerHumans(int bodyHeight, int x, int y) {
  fill(random(0, 360), 100, 100);
  ellipse(x, y - bodyHeight, faceDiameter, faceDiameter);                        // 顔
  rect(x - humanBodyWidth / 2, y - bodyHeight, humanBodyWidth, bodyHeight);      // 胴体
  if(bodyHeight > 3 * faceDiameter) {                   // 胴体の長さが3頭身を超えていれば
    bodyHeight = bodyHeight / 2;                           // 次に右に描く人の胴体の長さはその半分に

    // ここを考える

  }
}