その漫画自炊オタクはImageJマクロに恋をする

プログラミングを用いた、自炊漫画の画像処理

【ImageJマクロ超入門】#5 配列を使いこなす

 

f:id:yu3xx:20210509120806j:plain

 

 

皆さまこんにちは。

好きなB級映画ヒラリー・スワンク主演『11:14』です、yu3xx(ゆーさんちょめちょめ)です。

11:14 [DVD]

11:14 [DVD]

Amazon

 

 

 前回のおさらい

 

前回はifを使った「条件分岐」についてでした。

 

今回は少しややこしい「配列」の基本的な使い方と注意点についてです。

 

 

もくじ

 

配列って何?

 

配列とは、データの入ったタンスのことです。


配列を話題に出す時には、「要素」「要素数」「インデックス」という単語を使います。

 

カンタンに言うとこんな感じです。

 要素 → 各引き出しの中に入っているモノは何なのか?

 要素数 → 何個の引き出しを持つのか?

 インデックス → 何段目の引き出しに注目するのか?

 

要素を式で表現すると

 要素 = 配列名[インデックス]

となります。 

 

「段数」つまり「インデックス」は0から数え始めるのがポイントです。

 

例えば下記のような3段タンスの配列に「A」と名付ける場合、

 A[2] = 300

となります。

f:id:yu3xx:20210429062825p:plain

 

配列の作り方

 

ImageJマクロで配列を扱う際には、まずnewArrayという命令で「配列を定義」する必要があります。

//example 5-1

//作り方 その①
A = newArray(100, 110, 120);

//作り方 その②
v0 = 100;
v1 = 110;
v2 = 120;
B = newArray(v0, v1, v2);

//作り方 その③
C = newArray(3);
C[0] = 100;
C[1] = 110;
C[2] = 120;


//中身のカクニン
print("A=");
Array.print(A);

print("B=");
Array.print(B);

print("C=");
Array.print(C);

 

◆作り方 その①
newArray()の中に直接要素を書き込みます。要素と要素の間にはコンマ(,)を入れます。

 

◆作り方 その②
その1のアレンジ版。要素を直接書き入れる際に、値そのものではなく、変数名で指定します。

 

◆作り方 その③
まず引き出しの数を指定して、それから引き出しの中に何を入れるのかを指示します。
newArray(3)は引き出しが3つのタンスを用意したことになります。この時点ではすべての引き出しに0という値が入っています。
その後、「C[0]=~」のカタチで要素が何なのか指定していきます。


example5-1を実行すると、3つの方法がすべて同じ配列を作成していることがわかると思います。

f:id:yu3xx:20210511004205p:plain

Array.print(配列名)は、配列の要素をLogウィンドウで確認する命令です。

 

配列を使うメリット

 

配列を使うと、たくさんの変数を「配列名とインデックス」だけでスッキリ書くことができます。 

 

実例で考えてみます。

5人の身長の平均値を算出するプログラムを作ってみます。

 

配列を使わない書き方

//example 5-2

shinchou1 = 173.2;
shinchou2 = 162.9;
shinchou3 = 177.8;
shinchou4 = 153.5;
shinchou5 = 181.8;

sum = 0;

sum = sum + shinchou1;
sum = sum + shinchou2;
sum = sum + shinchou3;
sum = sum + shinchou4;
sum = sum + shinchou5;

ave = sum/5;

print(ave);

・身長の変数を5人分用意

・合計sumに1人分ずつ足していく

・sumを5で割る

 

という手順ですね。

 

配列を使う書き方

//example 5-3

SHINCHOU = newArray(173.2, 162.9, 177.8, 153.5, 181.8);

sum = 0;

for(i=0; i<5; i++){
	sum = sum + SHINCHOU[i];
}

ave = sum/5;

print(ave);

・5人全員の身長を格納する配列を作る 

・ループを回して合計sumを算出

・sumを5で割る

 

やっていることは完全に同じなんですが、すごくスッキリしています。

インデックスiをforループで回して繰り返し処理できる、というのも配列の魅力です。

 

配列のコピー方法

 

プログラミング上での配列のコピーには「参照渡し」と「値渡し」というものがあります。


ここが少しややこしいです。


カンタンに雰囲気だけで言ってしまうと、


「参照渡し」は、タンスそのものをコピーして増やすわけではなく、タンスの置いてある部屋への「入り口」を増やすことで、タンスへのアクセスルートを増やす方法。


「値渡し」は、タンスそのものをコピーして、「引き出しの中に入っているモノ」も含めて全部コピーする方法。

 

こんな感じです。わけわからん!って感じですよね。細かく説明していきます。

 

参照渡し

 

//example 5-4

A = newArray(0, 100, 200);

//参照渡しコピー
B = A;

//中身のカクニン
print("処理前のA = ");
Array.print(A);
print("処理前のB = ");
Array.print(B);

//Logの改行
print("");

//Bだけに処理
B[0] = B[0] + 1000;
B[1] = B[1] + 1000;
B[2] = B[2] + 1000;

//中身のカクニン
print("処理後のA = ");
Array.print(A);
print("処理後のB = ");
Array.print(B);

 

この例では「B = A;」という命令で配列Aを配列Bに「コピー」しています。このコピー方法は「参照渡しコピー」となります。

 

コピー後、「配列Bの要素だけに1000を足す」という処理をしています。


感覚的には、「Bの値だけが (1000, 1100, 1200) になる」ような気がしますよね。

 

ですが違うんです。実際には、

f:id:yu3xx:20210511010802p:plain

このように「AもBも、 (1000, 1100, 1200) になる」となっています。

 

何が起こったのかイメージ図で表現します。

 

f:id:yu3xx:20210511005439p:plain

 

つまり「参照渡し」で行われたのは、

「入り口A」で繋がっていた(100, 110, 120)のタンスがある部屋へ、無理やり「入り口B」を取り付けることで、Bからもタンスをいじることが出来るようにした。

という感じです。

 

この「参照渡し」では、「タンスをAとBで共有」しているので、Bから行った処理はAにも反映されるし、逆にAから行った処理はBにも反映されるという状態です。

 

機械上では、「参照渡し」で渡したものは「引き出しの中の値」ではなく、「タンスの置いてある場所はどこなのか?」ということになります。

 

うん。ややこしい!

 
値渡し

 

//example 5-5

A = newArray(0, 100, 200);

//値渡しコピー
B = newArray(3);
B[0] = A[0];
B[1] = A[1];
B[2] = A[2];

//中身のカクニン
print("処理前のA = ")
Array.print(A);
print("処理前のB = ")
Array.print(B);

//Logの改行
print("");

//Bだけに処理
B[0] = B[0] + 1000;
B[1] = B[1] + 1000;
B[2] = B[2] + 1000;

//中身のカクニン
print("処理後のA = ")
Array.print(A);
print("処理後のB = ")
Array.print(B);

 
まずnewArrayで要素数3のタンスを用意して、AのタンスからBのタンスへ「要素そのものをコピー」しています。

f:id:yu3xx:20210511010545p:plain
その後、「配列Bの要素だけに1000を足す」という操作をすると、予想通り「Bの値だけが (1000, 1100, 1200) になる」という結果でした。

 

これが「値渡し」です。イメージ図でも表現してみます。

 

f:id:yu3xx:20210511011050p:plain

 

「タンス」と「タンスの中に入っているもの」をまるっとコピーしています。


タンスが別個に存在するので、Bに行った処理はAには反映されません

 

うん。とても理解しやすいですね。


もっと書きやすい値渡しの方法

 

素数3ぐらいであれば、上記 example 5-5のように
 B[0] = A[0];
 B[1] = A[1];
 B[2] = A[2];
と書いても苦じゃないですが、素数が増えると大変です。


やり方としては、インデックスiをforループで回して「繰り返し処理」する方法もありますが、もう少しラクチンな方法もImageJには用意されています。example5-6をご覧ください。

 

//example 5-6

A = newArray(0, 100, 200);

//値渡しコピー
B = Array.copy(A);

//中身のカクニン
print("処理前のA = ")
Array.print(A);
print("処理前のB = ")
Array.print(B);

//Logの改行
print("");

//Bだけに処理
B[0] = B[0] + 1000;
B[1] = B[1] + 1000;
B[2] = B[2] + 1000;

//中身のカクニン
print("処理後のA = ")
Array.print(A);
print("処理後のB = ")
Array.print(B);

 

コピー先の配列名 = Array.copy(オリジナルの配列名);

 

この命令によって、簡単に「値渡し」のコピーを実行できます。

 

 

ちなみにですが、参照渡しコピーでも値渡しコピーでも、「渡し先の配列(B=Aの時のB、 B=Array.copy(A)の時のBのこと!)」はnewArrayで配列宣言しなくてもOKです!空気を読んで勝手に配列にしてくれます。

 

 

以上、「配列の作り方」から「参照渡し」と「値渡し」までの説明でした。

 

問題が起こるのは、「参照渡し」をしたあとで、「オリジナルの配列Aのデータをまた使いたい場合」です。

AとBが同じタンスを共有してしまうとデータが狂うので、こういう場合は「値渡し」でコピーしておきましょう。

 

 

今回の要点は、

 ・配列を扱う際には「参照渡し」と「値渡し」の違いを理解することが重要!

もうこれに尽きます!

 

 

ちなみに配列でややこしいのがもう一つありまして。

「自作関数」の「返り値」は、「参照渡し」で引き渡す。

という点です。

 

これは次回の「自作関数の作り方」でお話するので、こちらもどうぞ!

imagej-jisui.hatenablog.com

 

 


おしまい

 

 

 

 

・公開したマクロのまとめ

imagej-jisui.hatenablog.com