第10回(配列2)

一次元配列

復習

復習1: 配列の要素をインデックスで取り出す

次のプログラムを実行するとコンソールに何が表示されるか考えてみてください.

float[] ArrayA = {10.1, 20.2, 30.3, 40.4, 50.5};
println(ArrayA[0]);
println(ArrayA[2]);
println(ArrayA[4]);

復習2: int 型の配列を作る

要素数3の int 型の配列(名前は自由)を作り,次の値を代入してください。

80, 65, 90

その後,すべての値を次のようにコンソールに表示してください。

80
65
90

復習3: string型の配列を作る

要素数4の string 型の配列を作り,次の文字列を代入してください.

数学,英語,理科,社会

その後,すべての値を次のようにコンソールに表示してください.

数学
英語
理科
社会

復習4: 復習3をfor文で書いてください

復習5: 浮動小数点(配列ではないけれど)

次のプログラムを実行するとコンソールに何が表示されるか考えてみてください.

int a = 5;
int b = 3;
println(a/b);

1.5 を得たいときどうすればいいでしょうか.

説明すること

color型の配列

color型

たとえば,赤色を表す変数は次のように作ることができます.

color c;
c = color(255, 0, 0);

赤い円を描いてみます:

color c = color(255, 0, 0); // 上記コードを1行で書いた場合
size(100,100);
fill(c);
ellipse(50, 50, 40, 40);

このように色を変数に入れておくと,その色をあとから fill()stroke() に使うことができます.

color型の配列

複数の色をまとめて管理したい場合は color 型の 配列 を使います.

次は, color 型の変数を5個入れることのできる配列 (名前は colors) を作るという意味です.

color[] colors;
colors = new color[5];

配列の各要素には,次のように色を代入できます.

colors[0] = color(255, 0, 0); // 赤
colors[1] = color(0, 255, 0); // 緑
colors[2] = color(0, 0, 255); // 青
colors[3] = color(255, 255, 0); // 黄
colors[4] = color(0, 0, 0); // 黒

これを使って赤い円を描いてみます:

color[] colors;
colors = new color[5];

colors[0] = color(255, 0, 0); // 赤
colors[1] = color(0, 255, 0); // 緑
colors[2] = color(0, 0, 255); // 青
colors[3] = color(255, 255, 0); // 黄
colors[4] = color(0, 0, 0); // 黒

size(100,100);
fill(colors[0]);
ellipse(50,50,40,40);

setup()draw() などとの組合せ

setup()draw() と組合せて配列を使う場合,これまで使ってきた変数と同様,生成・代入のみのコードは外側に書けません.一方,宣言を含むコードは setup()draw() の外側に書いても良いです.

たとえば,

int a; // 宣言
a = 8; // 代入

void setup(){
    println(a);
}

というのはできませんでした.これと同様,

int[] A; // 宣言
A = new int[5]; // 生成

void setup(){
    println(A);
}

などはできません.

配列の意義

配列は,「同じ種類のデータをまとめて扱う」ための仕組みです.

似たような図形をたくさん描くだけなら,配列を使わなくても書ける場合があります.

たとえば,次のプログラムでは,60個の円を描くことができます.

for (int i = 0; i < 60; i++) {
ellipse(i * 10, i * 5, 10, 10);
}

この場合,円の位置は i から計算できます.そのため,x座標やy座標を配列に保存しておく必要はありません.

しかし,円ごとに違う値を持たせたい場合があります.

たとえば,

  • 円ごとに現在のx座標が違う
  • 円ごとに動く向きが違う
  • 円ごとに色が違う
  • 円ごとに速さが違う
  • その値を次のフレームでも使いたい

という場合です.

このようなとき,それぞれの円の情報を覚えておく場所が必要になります.そこで配列を使います.

float[] x = new float[5];
int[] muki = new int[5];
color[] colors = new color[5];

たとえば, x[i] は i番目の円のx座標, muki[i] は i番目の円の向き, colors[i] は i番目の円の色を表します.

for (int i = 0; i < x.length; i++) {
fill(colors[i]);
ellipse(x[i], 80 + i * 60, 30, 30);

x[i] = x[i] + muki[i];
}

このように,配列を使うと,複数の円がそれぞれ別々の値を持ち,その値を使って動くプログラムを書くことができます.

まとめると次のようになります.

for文
→ 同じ処理を繰り返すためのもの

配列
→ 複数の値を覚えておくためのもの

位置や色を毎回計算できるだけなら配列は必要ないこともあります。しかし,それぞれの円が自分の状態を持ち,その状態を次のフレームでも使う場合には,配列が便利です

本日の課題

先週の続きをしてください.

課題1:配列-基礎5

異なる型の配列を組み合わせたプログラムを実装してください:

  • 月〜金の曜日名を格納した String[] 型の配列 days を作成してください
  • 各曜日の気温(10〜30度のランダムな整数)を格納する int[] 型の配列 temperatures を作成してください
  • 「〇曜日の気温は〇度です」という形式でコンソールに表示してください
月曜日の気温は23度です
火曜日の気温は28度です
水曜日の気温は15度です
木曜日の気温は30度です
金曜日の気温は27度です

回答

String[] days = {"月","火","水","木","金"};
int[] temperatures = new int[5];

for(int i=0; i<days.length; i++){
  temperatures[i] = int(random(10,31));
  println(days[i]+"曜日の気温は"+temperatures[i]+"度です");
}
解説
String[] days = {"月","火","水","木","金"};

String[] 型の配列 days を宣言・生成・代入(初期化)しています.

days は,要素数も要素の値も最初から決まっているので,配列を作ると同時に値もまとめて代入しました.もちろん,宣言・生成・代入を分けても構いません.

int[] temperatures = new int[5];

int[] 型の配列 temperatures を宣言・生成しています.

new int[5] は,整数を5個入れることができる配列を作るという意味です.

days とは違い, temperatures の要素の値はまだ決まっていません.気温はランダムに決めるので,ここでは配列の場所だけを用意するため,宣言・生成までにとどめています.

for(int i=0; i<days.length; i++){
  temperatures[i] = int(random(10,31));
}

for 文を使って,配列 temperatures の各要素に気温を代入しています.

今回,配列の要素数 days.length5 です.そのため, for(int i=0; i<days.length; i++) とすることで, i0,1,2,3,4 と変化します.

temperatures[i] = int(random(10,31)); の行では, temperatures[i] にランダムな気温を代入しています.

ここで注意したいのは, int(float型の変数) は,小数点を切り捨てるということです. random(a,b)a 以上 b 未満の float 型を出力するため, random(10,31) とすることで 10.00... から 30.99... までの値が得られます.その値に対して int() を適用し小数点以下を切り捨てることで, 10 から 30 の整数が得られます.

ちなみに,このプログラムでは暗黙の約束として,

temperatures[0] は月曜日の気温, temperatures[1] は火曜日の気温,…

と考えています.つまり, daystemperatures は同じインデックスどうしが対応していると考えます.

もちろん,プログラム上は temperatures[0] をたとえば水曜日の気温と考えることもできなくはありません.しかし,そのようにすると,曜日と気温の対応がわかりにくくなります.

配列を複数使うときは,関連するデータを同じインデックスに入れると,プログラムが読みやすく,扱いやすくなります.

for(int i=0; i<days.length; i++){
  println(days[i]+"曜日の気温は"+temperatures[i]+"度です");
}

ここでは,各曜日の気温をコンソールに出力しています.コンソールに文字を表示するには, println() を使います.

月曜日から金曜日までの気温を5回表示したいので,ここでも for 文を使っています.意味的には, for(int i=0; i<days.length; i++) ではなく,気温を強調する意味で for(int i=0; i<temperatures.length; i++) でも曜日・気温関係なく単に5回繰り返したいというのを強調して for(int i=0; i<5; i++) でもよいのです.ただ, days.length とすると, temperatures[i] = int(random(10,31)); とまとめられるので本回答では days.length を採用しています.

課題2:配列-応用6

課題1のプログラムを拡張します.

  • 500×300のウィンドウを作成してください
  • 月〜金の各曜日の気温(10〜30度のランダムな整数)を配列に格納してください
  • 各曜日の気温を棒グラフで描画してください
    • 棒の幅は40,高さは `気温 × 5` とし,下端(y=height-50)から上に伸びるようにしてください
    • 棒の下に曜日名,棒の上に気温を表示してください →表示しなくてよいです
  • x軸・y軸を描画してください

回答

String[] days = {"Mon", "Tue", "Wed", "Thu", "Fri"};
int[] temperatures = new int[5];

void setup() {
  size(500, 300);
  background(255,255,255);


  for (int i = 0; i < days.length; i++) {
    temperatures[i] = int(random(10,31));
    fill(0,0,0);
    int x = 80*i+80;
    int y = height - (5*temperatures[i] + 50);
    rect(x, y, 40, 5*temperatures[i]);
    fill(255,0,0);
  }
  line(50,height-50, 450, height-50);
  line(50,height-50, 50, 50);
}
解説

まず 配列の宣言を setup() の外側に書く というところから整理します.

String[] days = {"Mon", "Tue", "Wed", "Thu", "Fri"};

int[] temperatures = new int[5];
void setup() {
    ...
}

ここで daystemperaturessetup() の 外側 に書いているのは,これらを グローバル変数 にするためです.

今回はすべての処理を setup() の中だけで行っているので外側に書く必要はありませんが, draw() でも同じ配列を使いたい場合には外側に書く必要が出てきます.

このように考える癖をつけておくとよいと思います.

int x = 80*i+80;
int y = height - (5temperatures[i] + 50);

ここが今回の課題のキモです.

まず xi 番目の棒の 左端のx座標 です. 80*i+80 なので, i=0 のとき x=80i=1 のとき x=160 ,…と80ピクセル間隔で並びます.

yi 番目の棒の 上端のy座標 です.

ここで Processingの座標系 について思い出してください.

画面の左上が原点 (0,0) で,下に行くほど y が大きくなります.

そのため,「下端 height-50 から上に伸びる棒」を rect() で描くには,棒の上端の y 座標が必要になります.

部分 意味
5*temperatures[i] 棒の高さ(気温×5)
+ 50 下端を height-50 にするための余白
height - 画面下から上へ向かって測る

つまり, height - (棒の高さ + 50) で,棒の 上端のy座標 を計算しています.

ちょっと頭の体操ですが,「画面下から測りたい距離」を計算してから height から引く, という考え方は Processingで 下端を揃えたい ときによく出てくるパターンです.

fill(0,0,0);
rect(x, y, 40, 5*temperatures[i]);

rect(x, y, 40, 5temperatures[i]) で棒を描いています.

rect() の引数は左上の座標と幅・高さなので, (x, y) から幅40・高さ 5temperatures[i] の長方形になります.

line(50,height-50, 450, height-50);
line(50,height-50, 50, 50);

棒グラフのx軸とy軸を直線で描きます.

line(x1, y1, x2, y2) は2点間に直線を引く関数です.

1行目はx軸(水平線),2行目はy軸(垂直線)です.

課題3:配列-応用9

4個の整数値を要素に持つ配列から様々な統計量を求めるプログラムを実装してください

  • 要素数4の配列には1〜99のランダムな整数を格納してください
  • 合計・平均・最大値・中央値を求め,コンソールに表示してください
  • 各要素が平均以上か平均未満かを判定して表示してください

できた人は中央値について,配列の要素数が奇数でも偶数でも同一のプログラムで実装できるように工夫してください.

配列の合計は181
配列の平均値は45
1番目の要素は90で平均以上です
2番目の要素は26で平均未満です
3番目の要素は32で平均未満です
4番目の要素は33で平均未満です
配列の最大値は90
配列の中央値は32.5

回答:

int[] A = new int[4];
int sumA=0; // 合計
int maxA=0; // 最大値
float medA; // 中央値

for(int i=0; i<A.length; i++){
  A[i] = int(random(1,100));
  sumA += A[i];
  if(maxA < A[i]){
    maxA = A[i];
  }
}

println("配列の合計は"+sumA);
println("配列の平均値は"+float(sumA)/A.length);

for(int i=0; i<A.length; i++){
  if(A[i] >= sumA/A.length){
    println((i+1)+"番目の要素は"+A[i]+"で平均以上です");
  } else{
    println((i+1)+"番目の要素は"+A[i]+"で平均未満です");
  }
}

println("配列の最大値は"+maxA);

int[] sortA = sort(A);
int medA1 = sortA[sortA.length/2];
int medA2 = sortA[(sortA.length-1)/2];
medA = (medA1+medA2)/2.0;
println("配列の中央値は"+medA);
解説
int[] A = new int[4];
int sumA=0; // 合計
int maxA=0; // 最大値
float medA; // 中央値

配列や変数を準備しています.

for(int i=0; i<A.length; i++){
    A[i] = int(random(1,100));
}

配列 A に 1から99までの整数を代入しています.

  • 合計(sumA)

    for(int i=0; i<A.length; i++){
        sumA += A[i];
    }
    

    合計を求めるときは「最初は0から始めて,要素を1個ずつ足していく」というのが基本パターンです.

    sumA += A[i]sumA = sumA + A[i] の省略形で,「今の sumAA[i] を足したものを,あらためて sumA に入れる」という意味です.こうすることで要素が足し合わされていきます.

    for(int i=0; i<A.length; i++) は配列Aのすべてのインデックスについて順に実行されるので, for文を回した暁には, sumA は要素をすべて足し合わせたものになっています.

    println("配列の合計は"+sumA);
    

    合計をコンソール表示しています.

  • 最大値(maxA)

    for(int i=0; i<A.length; i++){
        if(maxA < A[i]){
            maxA = A[i];
        }
    }
    

    maxA はこれまでに見た中での最大値を覚えておく変数です.

    最大値を求める if 文は,「今までの最大値より今見ている要素のほうが大きかったら最大値を更新する」という人間が手で最大値を探すときと同じやり方です.

    配列を一度だけスキャンしながら,これまでの記録を更新していく,というのはよく出てくるパターンです.

    ちなみに,今回は冒頭で 0 で初期化しています.ただしこれは「要素がすべて1以上」と分かっているからこう書いています.もし負の値を含む可能性がある配列なら, maxAint 型が扱える最小値 -2,147,483,648 などで初期化したり,先に A[0] を入れておくのが安全な書き方になります.

    別解として, 2つの値の最大値は次の式で求められます.

    \[\max(x, y) = \frac{x + y + |x - y|}{2}\]

    これを用いて次のようにif文を使わずに最大値を更新できます.

    for(int i=0; i<A.length; i++){
        maxA = (maxA + A[i] + abs(maxA - A[i]))/2;
    }
    

    ただしこれは if文より読みにくい ので,普通は if 文で書くか,組み込みの max() 関数を使います.

    for(int i=0; i<A.length; i++){
        maxA = max(maxA, A[i]);
    }
    
  • 平均値

    println("配列の平均値は"+float(sumA)/A.length);
    

    ここで わざわざ float(sumA) と書いているのは重要です.

    復習5でやった通り, int 同士の割り算は 小数点以下が切り捨てられます.

    たとえば 181 / 4 は数学的には 45.25 ですが, int 同士なら結果は 45 になってしまいます.

    そこで sumAfloat に変換してから割ることで,小数まで含めた割り算になります.ちなみに float(sumA)/A.lengthsumA/float(A.length) はどちらでも結果は同じです.

    片方が float なら,計算全体が float で行われるというルールがあるからです.

    for(int i=0; i<A.length; i++){
        if(A[i] >= float(sumA)/A.length){
            println((i+1)+"番目の要素は"+A[i]+"で平均以上です");
        } else{
            println((i+1)+"番目の要素は"+A[i]+"で平均未満です");
        }
    }
    

    A[i] と平均値を比較しています.平均値は配列 A の全ての要素に値が入ってから計算できるので,ここでif文を分けています.

  • 中央値

    中央値は少数になり得るので,冒頭で float 型にしています.

    int[] sortA = sort(A);
    int medA1 = sortA[sortA.length/2];
    int medA2 = sortA[(sortA.length-1)/2];
    medA = (medA1+medA2)/2.0;
    

    sort(A) は配列を昇順に並び替えた新しい配列を返します(元の配列 A は変わりません). sortA はここで新しく使用する変数なので型を宣言してください.

    中央値は

    • 要素数が奇数なら真ん中の要素
    • 要素数が偶数なら真ん中2つの要素の平均

    と本来は場合分けが必要です.

    要素数 n について次のように考えることができます.

    n n/2 (n-1)/2 sortA[n/2] sortA[(n-1)/2]
    5(奇数) 2 2 真ん中 真ん中(同じ要素)
    4(偶数) 2 1 真ん中の右側 真ん中の左側

    つまり, sortA[n/2]sortA[(n-1)/2] の平均をとれば,奇数のときは同じ要素同士の平均(=その要素自身),偶数のときは真ん中2つの平均 となり,どちらの場合も正しい中央値が得られます.

    また,最後に /2.0 と書いて わざと小数で割る ことで,中央値が小数になるケースにも対応しています.

課題4: 配列-応用7

複数の配列を使って,5つの円が独立して左右に跳ね返るアニメーションを実装してください:

  • 600×400のウィンドウを作成してください
  • x座標・向き・色をそれぞれ要素数5の配列で管理してください
  • 各円の初期x座標(100から80ピクセル間隔),向き(1),ランダムな色を設定してください
  • 5つの円を異なるy座標(80から60ピクセル間隔)に描画し,各円が独立して左右の壁で跳ね返るようにしてください

回答

float[] x = new float[5];
float[] y = new float[5];
int[] muki = new int[5];
color[] en_colors = new color[5];
int en_haba = 30;

void setup(){
  size(600,400);
  for(int i = 0; i<5; i++){
    x[i] = 100 + 80*i;
    y[i] = 80 + 60*i;
    muki[i] = 1;
    en_colors[i] = color(random(0,256),random(0,256),random(0,256));
  }
}

void draw(){
  background(255);
  for(int i=0; i<5; i++){

    fill(en_colors[i]);
    ellipse(x[i],y[i],en_haba,en_haba);

    x[i] +=muki[i];

    if(x[i] < en_haba/2 || x[i] > width-en_haba/2){
      muki[i] *= -1;
    }
  }
}
解説
float[] x = new float[5];
float[] y = new float[5];
int[] muki = new int[5];
color[] en_colors = new color[5];

4つの配列を setup()draw() の 両方の外側 に宣言しています.

これは グローバル変数 として宣言することで, setup() でも draw() でも,同じ配列にアクセスできるようにするためです.なぜ両方からアクセスする必要があるかというと,

setup() では,配列の 初期値 (初期位置や色)を一度だけ決める, draw() では,毎フレーム配列の 現在の値 に基づいて更新する,

という役割分担になっているからです.

ちなみに, xyint 型でも問題ありません.

void setup(){
    size(600,400);
    for(int i = 0; i<5; i++){
        x[i] = 100+ i*80;
        muki[i] = 1;
        en_colors[i] = color(random(0,256),random(0,256),random(0,256));
        y[i] = 80 + 60*i
    }
}

setup() は 最初に一度だけ 呼ばれる関数です.ここで配列の初期値を仕込んでおきます.

配列 初期値 意味
x[i] 100 + 80*i 100, 180, 260, 340, 420
y[i] 80 + 60*i 80, 140, 200, 260, 320
muki[i] 1 すべて右向き(=正方向)
en_colors[i] ランダムなRGB 円ごとに違う色

注目してほしいのは, 色の決定が setup() で行われている点です.もしこれを draw() の中でやってしまうと,毎フレーム色が変更されて点滅してしまいます.

「変わってほしくないもの」は setup() で,「毎フレーム変化するもの」は draw() で,というのが基本の使い分けです.

int en_haba = 30;
void draw(){
  background(255);
  ...
}

円の幅の情報は, ellipse() でもif文でも使用するので, en_haba として変数にしています.直接,30や15などの値を書いても動きますが,同じ意味をもつ値をプログラム中で何度も使う場合は,変数に入れて名前をつけておくと便利です.

background(255) を毎フレーム呼んでいるのは大事です.これを忘れると,前のフレームの円が画面に残り続けて 軌跡 のように見えます(それも面白い表現ですが).

void draw(){
  background(255);
  for(int i=0; i<5; i++){
    fill(colors[i]); // 色をつける
    ellipse(x[i],y[i],en_haba,en_haba); // 円を書く
    x[i] += muki[i]; // 円のx座標を変更することで円が動いているようにみせる
    if(x[i] < en_haba/2 || x[i] > width-en_haba/2){ // 跳ね返りの条件
      muki[i] *= -1;
    }
  }
}

コメントの通りです.配列を使用しない場合と考え方自体は同じです.

課題5: 配列-応用8

上述の課題4を拡張し,縦線のゾーンに応じて円の色が変わるアニメーションを実装してください:

  • 縦線をそれぞれランダムな色で100ピクセル間隔に描画してください
  • 各円は,いずれの縦線にも触れていないときは黒で描画してください
  • 各円が縦線に触れているときは,その縦線の色で描画することしてください.
    • ただし,画面端の x=0 および x=width における色の変化は,実装してもしなくてもよいです.実行例では, x=width では色が変化し, x=0 では黒のまま.

回答1(縦線についての配列の要素数が7)

float[] x = new float[5];
float[] y = new float[5];
int[] muki = new int[5];
color[] line_colors = new color[7];
int en_haba=30;

void setup(){
  size(600,400);

  for(int i = 0; i<5; i++){
    x[i] = 100 + 80*i;
    y[i] = 80 + 60*i;
    muki[i] = 1;
  }

  for(int i=0; i<line_colors.length; i++){
    line_colors[i] = color(random(0,256),random(0,256),random(0,256));
  }

}

void draw(){
  background(255);

  for(int i=0; i<line_colors.length; i++){
    stroke(line_colors[i]);
    line(100*i, 0, 100*i, height);
  }

  for(int i=0; i<5; i++){

    if(int((x[i]-en_haba/2)/100) == int((x[i]+en_haba/2)/100)){
      fill(0,0,0);
    } else{
      fill(line_colors[int((x[i]+en_haba/2)/100)]);
    }

    noStroke();
    ellipse(x[i],y[i],en_haba,en_haba);
    x[i] +=muki[i];

    if(x[i] < en_haba/2 || x[i] > width-en_haba/2){
      muki[i] *= -1;
    }

  }

}
解説

x, y, muki などは課題4の解説を見てください.

color[] line_colors = new color[7]; の意味は,縦線が 100,200,300,400,500 の5本に加えて,画面左端 x=0 と画面右端 x=600 も 線とみなして 7本扱いにしているからです.

// void setup()内
for(int i=0; i<line_colors.length; i++){
    line_colors[i] = color(random(0,256),random(0,256),random(0,256));
}

各縦線の色を定めています.

// void draw()内
for(int i=0; i<line_colors.length; i++){
    stroke(line_colors[i]);
    line(100*i, 0, 100*i, height);
}

定めた色で縦線を引きます.

  • 円と縦線の交差判定(剰余で判定)

    void draw(){
    
        // (略)
    
      for(int i=0; i<5; i++){
    
        if(int((x[i]+en_haba/2)/100) == int((x[i]-en_haba/2)/100)){
          fill(0,0,0);
        } else{
          fill(line_colors[int((x[i]+en_haba/2)/100)]);
        }
    
        // (略)
    
        }
    
      }
    }
    

    ここが本課題のポイントです.

    if文で円が縦線に触れているかの判定を行い,触れていなければ黒,そうでなければ縦線の色と指示しています.

    考え方を整理します.ウィンドウを縦線で領域に区切ると,

    • 領域0: 0 ~ 99
    • 領域1: 100 ~ 199
    • 領域2: 200 ~ 299

    と考えることができます.

    このとき,あるx座標 p がどの領域にあるかは, int(p/100) で決まります.たとえば, p=150 なら, int(150/100) = 1 で領域1にあることがわかります.

    ここで円の中心は x[i], 半径 en_haba/2 なので,円の左端は x[i] - en_haba/2 ,円の右端は x[i] + en_haba/2 にあります.

    • 円が縦線に触れていない → 左端も右端も 同じ領域 にある
    • 円が縦線に触れている → 左端と右端が 違う領域 にある

    と考えられるので, int((x+en_haba/2)/100) == int((x-en_haba/2)/100) なら線に触れていない,そうでなければ 線に触れている と判定できます.

  • 円の縦線の交差判定(差で判定)

    別解です. void draw() が以下のようになります.

    void draw(){
      background(255);
    
      for(int i=0; i<line_colors.length; i++){
        stroke(line_colors[i]);
        line(100*i, 0, 100*i, height);
      }
    
      for(int i=0; i<5; i++){
    
        fill(0,0,0);
    
        for(int j=0; j<line_colors.length; j++){
          if(abs(x[i] - 100*j) <= en_haba/2){
            fill(line_colors[j]);
          }
        }
    
        noStroke();
        ellipse(x[i],y[i],en_haba,en_haba);
        x[i] +=muki[i];
    
        if(x[i] < en_haba/2 || x[i] > width-en_haba/2){
          muki[i] *= -1;
        }
    
      }
    
    }
    

    剰余で判定する場合との差分は以下の部分です.

    for(int i=0; i<5; i++){
    
        fill(0,0,0);
        for(int j=0; j<line_colors.length; j++){
            if(abs(x[i] - 100*j) <= en_haba/2){
            fill(line_colors[j]);
            }
        }
    
        // (略)
    
    }
    

    考え方は単純で,円の中心 x[i] と縦線の位置 100*j の 距離 (絶対値で測る)が半径 en_haba/2 より小さければ, 円はその線と重なっている ということです.

    もし絶対値で測るのがわかりにくい人は展開して

    if(x[i] < 100*j+en_haba/2 && x[i] > 100*j-en_haba/2)

    としてください.

回答2(縦線についての配列の要素数が5)

回答だけ乗せておきます.この場合,剰余より差で計算したほうがわかりやすいですね.

float[] x = new float[5];
float[] y = new float[5];
int[] muki = new int[5];
color[] line_colors = new color[5];
int en_haba = 30;

void setup(){
  size(600,400);

  for(int i = 0; i < x.length; i++){
    x[i] = 100 + 80*i;
    y[i] = 80 + 60*i;
    muki[i] = 1;
  }

  for(int i = 0; i < line_colors.length; i++){
    line_colors[i] = color(random(0,256), random(0,256), random(0,256));
  }
}

void draw(){
  background(255);

  for(int i = 0; i < line_colors.length; i++){
    stroke(line_colors[i]);
    line(100*i + 100, 0, 100*i + 100, height);
  }

  for(int i = 0; i < x.length; i++){

    fill(0,0,0);

    for(int j = 0; j < line_colors.length; j++){
      int line_x = 100*j + 100;

      if(abs(x[i] - line_x) <= en_haba/2){
        fill(line_colors[j]);
      }
    }

    noStroke();
    ellipse(x[i], y[i], en_haba, en_haba);

    x[i] += muki[i];

    if(x[i] < en_haba/2 || x[i] > width - en_haba/2){
      muki[i] *= -1;
    }
  }
}

課題6: 配列-応用10

6個の整数値を要素に持つ配列を逆順に並び替えるプログラムを実装してください:

  • {15, 10, 3, 7, 11, 8} の6要素の配列を定義してください
  • reverse() を使用せず,配列を逆順に並び替えた 配列を得てください.
    • コンソール表示の結果が逆順になっているだけではだめです.
  • 並び替え前後で println()printArray() してプログラムがうまく動いているかを確認してください.

この問題には主に次の2つの解き方があります。

  1. 逆順の値を入れるために,新しい配列をもう1つ作る方法
  2. 新しい配列を作らず,元の配列の要素を入れ替える方法

1. は比較的簡単かと思います. 2. の方法で解いてみましょう.

並べ替え前
[0] 15
[1] 10
[2] 3
[3] 7
[4] 11
[5] 8
並べ替え後
[0] 8
[1] 11
[2] 7
[3] 3
[4] 10
[5] 15

回答

int[] A = {15, 10, 3, 7, 11, 8};
println("並べ替え前");
println(A);
for(int i=0; i<A.length/2; i++){
  int tmp = A[i];
  A[i] = A[A.length-1-i];
  A[A.length-1-i] = tmp;
}
println("並べ替え後");
println(A);
解説

逆順に並び替えるには,

  • 一番左(i=0)と一番右(i=A.length-1)を入れ替え
  • 左から2番目(i=1)と右から2番目(i=A.length-2)を入れ替え

というように, 両端から中央に向かって入れ替えていきます.

i 入れ替える相手 (要素数6の場合)
0 A.length-1 = 5 A[0] ↔ A[5]
1 A.length-2 = 4 A[1] ↔ A[4]
2 A.length-3 = 3 A[2] ↔ A[3]

つまり, A[i]A[A.length-1-i] を入れ替えればよいです.

ただし,2つの変数の値を入れ替えるとき,単純に代入してもうまくいきません.たとえば A[0]A[5] を入れ替えたいとして,こう書くと失敗します.

A[0] = A[5];  // ① A[0]に A[5]の値を入れる
A[5] = A[0];  // ② A[5]に A[0]の値を入れる ← しかし①でA[0]はもうA[5]になっている...

①の時点で A[0] の元の値は消えてしまうので,②では結局 A[5] に元の値が戻り,両方とも元の A[5] の値になってしまいます.

そこで, 一時的な避難場所として tmp という変数を使います. (名前はなんでも良いです.)

int tmp = A[0];      // A[0]の元の値を tmp に避難
A[0] = A[5];         // A[0]に A[5]の値を入れる
A[5] = tmp;          // A[5]に 避難させた値を入れる

これはプログラミングでよく使うテクニックの一つです.

for(int i=0; i<A.length/2; i++){}

残る謎は,なぜループは A.length/2 まで?ということです.

もしループを i<A.length まで回してしまうと,

i=0A[0] ↔ A[5] を入れ替え i=5A[5] ↔ A[0] を入れ替え ← 元に戻ってしまう!

というように, 一度入れ替えたものをもう一度入れ替えて元に戻すことになります.

これでは結局元の配列に戻ってしまうのでループは半分まで (A.length/2)で止めます.