7 配列

Chapelの配列には、矩形配列連想配列の2種類が存在する。また、類似の概念に、タプルレンジ領域が存在する。

7.1 タプル

タプルは、複数の要素をカンマ区切りで並べた値である。値型で、要素の型が揃う必要もなく、配列よりも軽量である。
要素の型を揃えた場合は、要素の数と型の乗算により、タプル型を表現できる。また、theseイテレータが利用できる。

const nums: 9 * int = (60, 45, 53, 163, 90, 53, 165, 75, 60);
const boys: (string, string, string) = ("Tom", "Ken", "Bob");
writeln(boys(0), boys(1), boys(2), for name in boys do name); // TomKenBobTom Ken Bob

タプルは、複数の変数を同時に宣言する場合や、値を同時に書き込む場合に利用できる。この機能をアンパックと呼ぶ。

var (a, b): (string, int) = ("student", 24);
writeln(a, b);

タプルのアンパック機能は、関数の宣言でも利用できる。タプルで宣言された引数には、タプルの値を渡す必要がある。

proc foo(x: int, (y, z): (int, int)): int return x * (y + z);
writeln(foo(2, (3, 4))); // 14

7.2 レンジ

レンジは、整数の有限区間や半無限区間や無限区間を表す値である。値型で、同様の機能を持つ領域よりも軽量である。

const from100To200: range(int, boundedType = BoundedRangeType.bounded) = 100..200;
const from100ToInf: range(int, boundedType = BoundedRangeType.boundedLow) = 100..;
const fromInfTo200: range(int, boundedType = BoundedRangeType.boundedHigh) = ..20;

byで刻み幅を指定できる。その場合は、alignで指定された値を必ず含む。また、#でレンジの要素の個数を指定できる。

writeln(10..30 by -7);              // 30 23 16
writeln(10..30 by -7 align 13);     // 27 20 13
writeln(10..30 by -7 align 13 # 2); // 27 20

レンジに含まれる要素の個数は.sizeで、区間の下限は.lowで、区間の上限は.highで、刻み幅は.strideで得られる。

writeln((100..200).size);      // 101
writeln((100..200).low);       // 100
writeln((100..200).high);      // 200
writeln((100..200).stride);    //   1
writeln((100..200).alignment); // 100

レンジでは、theseイテレータが利用できる。for文に限らず、並列化されたforall文やcoforall文でも利用できる。

for i in 1..100 do writeln(i);
forall i in 1..100 do writeln(i);
coforall i in 1..100 do writeln(i);

7.3 領域

領域は、配列の定義域や値の集合を表す。レンジのタプルと等価な矩形領域と、要素を格納した連想領域の2種類がある。
領域の要素の型は.idxTypeで取得できる。特に、矩形領域は、.rankで階数を、.dimsでレンジのタプルを取得できる。

const rectangular: domain = {0..10, -1..2};
const associative: domain = {"foo", "bar"};
writeln(rectangular.rank);
writeln(rectangular.dims());
writeln(rectangular.idxType: string); // int(64)
writeln(associative.idxType: string); // string

矩形領域も連想領域も、theseイテレータが利用できる。特に、矩形領域は、多重ループを表す構文としても利用できる。

for xyz in {1..10, 1..10, 1..10} do writeln(xyz);
for boy in {"Tom", "Ken", "Bob"} do writeln(boy);

7.4 配列

配列は、定義域から値域への写像を表す。矩形領域に対応する矩形配列と、連想領域に対応する連想配列の2種類がある。

const rectangular = [1, 2, 3, 4, 5, 6, 7, 8];
const associative = [1 => "one", 2 => "two"];
writeln(rectangular, rectangular.domain); // 1 2 3 4 5 6 7 8{0..7}
writeln(associative, associative.domain); // one two{1, 2}

thisメソッドが利用でき、要素の位置を引数に渡せば、その要素を参照できる。領域を渡せば、部分配列も参照できる。

var A: [{1..10, 1..10}] real;
var B: [{'foo', 'bar'}] real;
A[1, 2] = 1.2;
A(3, 4) = 3.4;
writeln(A(1..2, 1..3));

theseイテレータも利用できる。左辺値を返すので、for文でループ変数に値を書き込むと、その値が配列に反映される。

var boys = ['Tom', 'Ken', 'Bob'];
for boy in boys do boy += '-san';
writeln(boys); // Tom-san Ken-san Bob-san

祖な矩形領域を定義域に指定すると、粗な配列を宣言できる。これは、粗行列の実装として活用できる。以下に例を示す。

var D: sparse subdomain({1..16, 1..64});
var A: [D] real;
D += (8, 10);
D += (3, 64);
A[8, 10] = 114.514;

配列は値型であり、配列を他の配列に代入すると、値が複製される。ただし、関数に配列を渡す場合は、参照渡しになる。

proc update(arr: [] int) {
  arr = [2, 3, 4];
}
var A = [1, 2, 3];
var B = A;
update(A);
writeln(A); // 2 3 4
writeln(B); // 1 2 3