ChucK : Language Specification > Arrays

ChucK : 言語仕様 > 配列

version: 1.2.x.x (dracula)

ChucK - [Language Specification : Arrays]

配列

配列とは、N 次元の整列した (同じ型の) データの集合を表したものです。
この節では、ChucK において配列がどのように宣言され、また扱われるのかを述べます。
以下はいくつかの簡単なメモです:

  • 配列は整数 (0 から数えられます) により順序付けされます。
  • 全ての配列は連想配列 (文字列を使った辞書) として扱うこともできます。
  • 整数によって順序付けされる部分と連想配列とは、異なる名前空間に保存されることを忘れないでください。
  • 配列はオジェクト (objects and classes を参照のこと) であり、その他の参照型と同様に代入や操作が可能です。

配列についてはサンプルコードも参照してください。

宣言

配列は以下のようにして宣言されます。

// 10 個の int 型の要素を持つ foo と言う名前の配列変数を宣言する
int foo[10];

// int (プリミティブ型) の配列であるため、配列の中身は
// 自動的に 0 に初期化されます

以下のようにして配列を初期化することもできます。

// chuck による配列の参照の初期化
[ 1, 1, 2, 3, 5, 8 ] @=> int foo[];

上記のコードには、いくつかの特記事項があります。

  • 配列の初期化式には、同じ型または類似の型の値を含む必要があります。それら全ての要素の最も基底となる型を探し出そうとします。もし、要素の間に基底となる型が存在しない場合はエラーとなります。
  • 初期化式 [ 1, 1, 2, 3, 5, 8] の型は int[] になります。初期化式は配列そのものであり、配列を使いたい場所に直接記述することができます。
  • at-chuck 演算子 (@=>) は参照の割り当てを意味しており、その詳細については operators and operations にて記載があります。
  • int foo[] では空っぽの配列の参照を宣言しています。この文は、配列 foo に初期化式の評価値を割り当てています。
  • 配列はオブジェクトです。

オブジェクトの配列を宣言した場合は、自動的に作成されるインスタンスで配列が初期化されます。

// 配列に自動的に作成されたインスタンスが割り当てられます
Object group[10];

オブジェクトへの参照の配列のみが欲しい場合には以下のようにします:

// null オブジェクトへの参照の配列
Object @ group[10]

Chuck におけるオブジェクトの代入とインスタンス作成の詳細については Check here。

上記の例では一次元の配列 (またはベクタ) を宣言しました。次節では多次元配列について述べます。

多次元配列

以下のようにすることで多次元の配列を宣言することもできます:

// float 型の 4 * 6 * 8 の配列を宣言
float foo3D[4][6][8];

同様に初期化式も使えます:

// int 型の 2 * 2 の配列を宣言
[ [1,3], [2,4] ] @=> int bar;

上記のコードでは、行列 (matrix) を作っているため として宣言しています。

参照

配列中の要素には [] を使ってアクセスできます。(宣言した大きさを越えない範囲で)

// float 型の配列を宣言
[ 3.2, 5.0, 7 ] @=> float foo[];

// 0 番目の要素にアクセス (デバッグ表示させます)
<<< foo[0] >>>; // きっと 3.2

// 2 番目の要素をセットします
8.5 => foo[2];

配列中の全ての要素を取り出します:

// またもや float 型の配列
[ 1, 2, 3, 4, 5, 6 ] @=> float foo[];

// 配列全体をループします
for( 0 => int i; i < foo.cap(); i++ )
{
    // 何かします (デバッグ表示)
    <<< foo[i] >>>;
}

多次元配列にアクセスします:

// 2 次元配列
int foo[4][4];

// 要素をセットします
10 => foo[2][2];

もしも配列のインデックスがその次元における配列の大きさを越えてしまった場合、例外が発生して現在のシュレッド (shred) が停止します。

// 配列の大きさは 5
int foo[5];

// これは ArrayOutOfBoundsException を引き起こします
// 6 番目の要素にアクセス (インデックス 5)
<<< foo[5] >>>;

ちょっと長いプログラム: example から otf_06.ck:

// 周期
.5::second => dur T;
// 周期に同期させる (一括処理の際の同期のため)
T - (now % T) => now;

// パッチの定義
SinOsc s => JCRev r => dac;
// 初期化
.05 => s.gain;
.25 => r.mix;

// スケール (ペンタトニック; 半音づつ)
[ 0, 2, 4, 7, 9 ] @=> int scale[];

// 無限ループ
while( true )
{
    // スケール上からどれか音を選び出す
    scale[ Math.rand2(0,4) ] => float freq;
    // 周波数を求める
    Std.mtof( 69 + (Std.rand2(0,3)*12 + freq) ) => s.freq;
    // reset phase for extra bandwidth
    0 => s.phase;

    // 時間を進めます
    if( Std.randf() > -.5 ) .25::T => now;
    else .5::T => now;
}

連想配列

全ての配列は文字列をキーとした連想配列として使用することも可能です。通常の配列を作成した後、特別な操作は何もせずとも、そのまま連想配列として扱うことができます。

// declare regular array (capacity doesn't matter so much)
// 通常の配列を宣言 (ここで宣言する配列の大きさは連想配列には関係ありません)
float foo[4];

// 通常の配列として使用
2.5 => foo[0];

// 連想配列として使用
4.0 => foo["yoyo"];

// 連想配列にアクセス (デバッグ表示)
<<< foo["yoyo"] >>>;

// 存在しない要素にアクセス
<<< foo["gaga"] >>>;  // -> 0.0 として表示されるはず

大切なことなので再度記載しますが、整数によって順序付けされる部分と連想配列とは完全に異なる名前空間に分割されています。例えば以下のように:

// 配列の宣言
int foo[2];

// 0 番目の要素として何かを代入
10 => foo[0];

// "0" に何かを代入
20 => foo["0"];

// 10 20 が表示されます
<<< foo[0], foo["0"] >>>;

宣言された際の配列の大きさは整数によって順序付けされる配列にのみ適用されます。例えば、長さ 0 の配列を作成した場合でも、連想配列としてはいくつでも要素を持たせることができます。

// 長さ 0 の配列を宣言
int foo[0];

// "here" に何かを代入
20 => foo["here"];

// this should print out 20
// 20 が表示されます
<<< foo["here"] >>>

// 例外が発生します
<<< foo[0] >>>

注意: 通常の配列の場合とは異なり、連想配列に含まれる要素は全て明示的に初期化されるものであるため、連想配列の長さは事前に指定されません。

連想配列の初期化されていない要素は null への参照を返します。ChucK のオブジェクトと参照についての説明は class documentation page を参照してください。

class Item { 
   float weight; 
}

Item box[10]; 

// 整数のインデックスは事前に初期化されています
1.2 => box[1].weight; 

// "lamp" に新しいインスタンスを代入します
new Item @=> box["lamp"]; 

// "lamp" へアクセスすることができるようになりました
2.0 => box["lamp"].weight; 

// オブジェクトが存在しないため NullPointerException が発生します
2.0 => box["sweater"].weight; 

配列への代入

配列はオブジェクトです。そのため、配列を宣言した場合、私たちは実際には (1) 配列への参照 (ポインタ変数) を宣言して、(2) 新しい配列を生成してその参照を変数に割り当てるということを行なっています。空の参照とは、オブジェクトではないものか null を指し示すように宣言された参照です。空の参照を持つ配列変数は以下のようにして宣言できます:

// 配列への参照を宣言します (配列の長さは指定しません)
int foo;

// この時点で foo には int なら何でも代入できます
[ 1, 2, 3 ] @=> foo;

// 0 番目の要素を表示します
<<< foo[0] >>>;

関数の引数や返値として配列を用いることもできます。

// print を定義します
fun void print( int bar[] )
{
    // 表示させます
    for( 0 => int i; i < bar.cap(); i++ )
        <<< bar[0] >>>;
}

// リテラルの配列初期化式を直接関数に渡すことができます
print( [ 1, 2, 3, 4, 5 ] );

// もしくは、参照変数として配列を渡すこともできます
int foo[10];
print( foo );

その他のオブジェクトと同様に、ある配列への参照をいくつも作成することができます。その他のオブジェクトと同様に、全ての代入は参照の代入となるため、データのコピーは行なわれずに配列への参照のみがコピーされます。

// 配列を宣言します
int the_array[10];

// 配列の参照を foo と bar に代入します
the_array => int foo => int bar;

// (この時点で the_array と foo と bar は全て同じ配列への参照です)

// the_array に変更を行なってから foo を表示すると...
// 同じ配列への参照であるため、どれかに変更を加えると全てに変更が加えられたように見えます
5 => the_array[0];
<<< foo[0] >>>; // 5 と表示される

多次元配列のうち一部の部分配列への参照を得ることもできます。

// 三次元配列
int foo3D[4][4][4];

// 部分配列への参照を得ることができます
foo3D[2] => int bar;

// (ここで指定する次元は正しいものでなければいけません!)