配列¶
概要¶
演算子 [ ] は配列要素にアクセスするための演算子。配列には、文字列を分割して擬似的に配列と見なす簡易配列と、コンマ区切りで列挙された要素を扱う汎用配列の 2 種類がある。
簡易配列¶
文字列に含まれるコンマをデリミタ(区切り文字)と解釈して、配列のように扱う機能。
request
{
_a = "this,is,a,pen"
_a[1]
}
request は "is" を出力する。[ ] 演算子が処理する対象は変数である必要はなく、即値でも関数の返値でもよい。上の例は次のように書いても同じ。
request
{
"this,is,a,pen"[1]
}
カスタムデリミタ¶
序数の後ろにコンマ区切りでデリミタを指定すると、コンマでなく他の文字列で簡易配列の要素を区切れる。
request
{
"This is a island."[2,"is"]
}
"is" で区切るので、文字列は次のように分解される。
| 序数 | 要素 |
|---|---|
[0] |
"Th" |
[1] |
" " |
[2] |
" a " |
[3] |
"land." |
したがって request は " a " を返す。
多次元配列風のアクセス¶
デリミタ指定をうまく使うと、多次元配列風に値を取り出せる。
request
{
_ar = "taro|male,ayame|female,hotaru|female"
_ar[2][1,"|"]
}
_ar[2] は "hotaru|female" になる。さらに | を区切り文字として解釈して [1] を取り出すので、結果は "female" になる。このように、階層ごとにユニークなデリミタを適用することで、任意の位置の値を簡単に抜き出せる。
範囲外の序数を指定した場合の出力については、範囲外の要素を参照。
簡易配列と汎用配列を利用した多次元配列¶
もっと直接的に多次元配列を扱いたいなら、
Food = "うどん,ラーメン,おにぎり"
Color = "赤,青,黄"
と簡易配列を宣言し、汎用配列で
Word = (Food, Color)
とすると、Word[0][0] で「うどん」、Word[1][2] で「黄」が得られる。
代入¶
ここからは変数限定の機能。要素に代入できる。
request
{
_a = "this,is,a,pen"
_a[3] = "eraser"
_a
}
"pen" を "eraser" に書き換えていて、request の実行結果は "this,is,a,eraser" になる。デリミタを指定しても正しく機能する。
request
{
_s = "This is a island."
_s[2,"is"] = " beautiful "
_s
}
request は "This is beautiful island." を出力する。
多次元配列風に [ ] 演算子を連結して使っている場合の代入は、Tc600-3以降で使える(値の入れ子と多次元代入)。それ以前のバージョンでは、代入が可能なのは一次元の場合のみで、次の操作はエラーになった。
request
{
_ar = "taro|male,ayame|female,hotaru|female"
_ar[1][1,"|"] = "male"
}
現在の要素数を超える位置へも問題なく代入できる。デリミタが自動的に追加され、要素数が拡張される。
request
{
_m = "fuji/asama/tanigawa"
_m[5,"/"] = "daisen"
_m
}
request は "fuji/asama/tanigawa///daisen" を出力する。
SETDELIM¶
SETDELIM 関数を使うと、「デフォルトのデリミタ」をコンマから別の文字列へ変更できる。先に示した例を SETDELIM を使って書き換えたものを示す。
request
{
_m = "fuji/asama/tanigawa"
SETDELIM(_m, "/")
_m[5] = "daisen"
_m
}
SETDELIM することにより、単純に _m[5] と書けるようになる。多次元配列風に [ ] 演算子を連結して使っている場合、SETDELIM は最初の(一次元目の)[ ] のみで有効。
汎用配列¶
汎用配列は、さまざまな型の値を混在して格納できる配列構造。一般的なアクセスでは簡易配列より高速に動作する。
初期化¶
i = (100,"test",-1.5)
要素をコンマで列挙して記述する。代入する場合は、上のように要素の集合を ( ) で囲むこと。コンマの演算優先度は代入よりも低いため、ブラケットが無いと、
i = 100,"test",-1.5
は (i = 100),"test",-1.5 と解釈され、i には 100 だけが入る。
配列を空の状態で初期化するには、IARRAY という関数を使う。IARRAY は「空の汎用配列」を返す関数。
i = IARRAY
初期化時に要素をひとつだけ代入したい場合は工夫が要る。たとえば単に i = 100 としたのでは、配列ではなくただの数値の代入になってしまうから。次のように記述する。
i = (IARRAY,100)
要素追加¶
i = (i,"add")
とすると、配列の後端に "add" が追加される。配列を追加することもできる。
i = (i,("add",123,0.0))
a = a + 1 を a += 1 と略せるように、上の例は次のようにも書ける。
i ,= ("add",123,0.0)
先頭への挿入も同じ書式でできる。
i = ("first",i)
中途への挿入もできる。
i = (100,200,300,400,500,600)
i[2] ,= "insertion"
i は (100,200,300,"insertion",400,500,600) になる。挿入される位置は [2] ではなく [3] になることに注意。i[2] へ挿入したい場合は次のようにする。
i[2] = ("insertion",i[2])
要素削除¶
削除したい要素へ IARRAY を代入する。
i = (100,200,300,400,500,600)
i[2] = IARRAY
300 が削除され、i は (100,200,400,500,600) になる。
値の更新¶
単純に要素へ代入できる。現在の要素数を超える位置へも問題なく代入でき、必要な数だけ要素数が拡張される。
値の取り出し¶
通常の変数と同様、序数を指定して記述すればそのまま出力される。範囲外の序数を指定した場合の出力については、範囲外の要素を参照。
i = (100,200,300,400,500,600)
i[4]
500 が出力される。[ ] 演算子が処理する対象は変数である必要はなく、即値でも関数の返値でもよい。
(100,200,300,400,500,600)[4]
500 が出力される。汎用配列をそのまま関数の出力にもできる。
request
{
river[2]
}
river
{
("tenryu","bandou-tarou","ishikari","shimanto")
}
request は "ishikari" を出力する。
配列の中に配列は書けない¶
( ) の中に書いた ( ) は、副次的な配列とは認識されず、展開されて単純に結合される。
(100,200,(300,400),500,600)
と書いても、結局次のように結合される。
(100,200,300,400,500,600)
要素に配列やハッシュを入れたいときは、次の「入れ子と多次元代入」を参照。
演算¶
要素単位の演算は通常に行える。
汎用配列と単項値との演算は、「全要素へ単項値が演算される」という独特の動作になる。
pref = ("gunnma","ohsaka","hokkaido")
pref += "-ken"
pref は "gunnma-ken","ohsaka-ken","hokkaido-ken" になる。
answer = (2*(1,2,3))[1]
answer には 4 が代入される。2*(1,2,3) の計算結果が (2,4,6) になるから。
汎用配列どうしの演算は、演算可能なすべての組み合わせを出力する。
pref1 = ("gunma","osaka","hokkai")
pref2 = ("-ken","-fu","-do")
pref3 = pref1 + pref2
pref3 は "gunma-ken","gunma-fu","gunma-do","osaka-ken","osaka-fu","osaka-do","hokkai-ken","hokkai-fu","hokkai-do" になる。配列に数値(整数、実数)が入っている場合は、引き算・割り算・掛け算など他の演算も有効。
要素の比較¶
配列同士を == で比べると、要素ごとに比べる。Tc600-3以降では、要素が両方とも VOID のときも等しいとみなす(それまでは偽だった)。
関数の引数¶
YAYA において関数の引数は汎用配列。_argv の内容は、呼び出し側の引数がそのまま代入された汎用配列になっている。つまり
func(1, 2, "test")
という関数の呼び出しは、
_i = (1,2,"test")
func(_i)
とも書ける。非常にトリッキーな表記だが、これで意図どおりに動作する。書き直したスクリプトでも、引数の数は 1 個ではなく 3 個である点に注意。
この構造をうまく利用すると、可変長の引数を他の関数へ簡単に渡せる。
request
{
total(1,2,3,4,5,6)
}
total
{
calc_total(_argv)
}
calc_total
{
_answer = 0
foreach _argv; _val { _answer += _val }
_answer
}
total は自分では何もせず、すべての引数をそのまま calc_total へ引き渡している。この例では単純に引き渡しているが、もちろん必要に応じて加工してから渡すこともできる。
引数の指定方法が複雑になっている場合は注意すること。
_i = (1,2,"test")
func("sky", _i, "sun")
上の呼び出しは次と等価。汎用配列は多次元化できない(展開される)ことを思い出すこと。
func("sky", 1, 2, "test", "sun")
デリミタ/取得数指定¶
_i = (2,"is")
"This is a island."[_i]
簡易配列のデリミタ指定の部分も汎用配列なので、上のようなこともできる。これは次の記述と等価。
"This is a island."[2,"is"]
入れ子と多次元代入(Tc600-3以降)¶
配列の要素にハッシュを入れたり、ハッシュの値に配列を入れたりできる。読み出しは _a[1]["x"]、代入は _a[1]["x"] = 5 のように [ ] を続けて書く。
_a = (0, IHASH("x", 1), 2)
_a[1]["x"] // 1
_a[1]["x"] = 5 // 多次元代入
多次元代入では、途中の要素が文字列なら簡易配列として書き換える:
_a = ("p,q", "r")
_a[0][1] = "Q" // _a[0] は "p,Q"
ただし、汎用配列の中に新しく汎用配列を入れる手段は無い。, 演算子も _a[1] = (8,9) も、配列を展開して差し込む。代入先の要素がすでに配列やハッシュのときだけは、展開せずに丸ごと置き換える(Tc603-1以降。内側の配列やハッシュを丸ごと書き換える)。
詳細は 値の入れ子と多次元代入 を参照。
範囲指定¶
簡易配列・汎用配列とも、序数を範囲で指定できる。取得、代入ともに可能。範囲は汎用配列で指定する。たとえば i[a,b] は「i の要素 a〜b」を表す。
name = ("さくら","せりこ","奈留","まゆら","毒子","美耳")
i = name[1,3]
name[3,4] = "奎子"
j = name
name[0,2] = IARRAY
k = name
iには("せりこ","奈留","まゆら")が格納されるjは("さくら","せりこ","奈留","奎子","美耳")になるkは("奎子","美耳")になる
対象が簡易配列の場合でも書式は同じ。
name = "さくら,せりこ,奈留,まゆら,毒子,美耳"
i = name[1,3]
name[3,4] = "奎子"
j = name
i は "せりこ,奈留,まゆら"、j は "さくら,せりこ,奈留,奎子,美耳" になる。
デリミタ指定もできる。範囲指定のすぐ後ろに続けて指定する。
animal = "くま!うさぎ!ねこ!いぬ!わに"
i = animal[0,2,"!"]
animal[2,4,"!"] = "ぶた"
j = animal
i は "くま!うさぎ!ねこ"、j は "くま!うさぎ!ぶた" になる。
範囲の端の扱い¶
a > bのとき(i[3,1])は a と b を入れ替えて、i[1,3]と同じ範囲になる- 末尾を超える側は、末尾までとして扱う。
n = (1,2,3,4)のときn[2,9] = 9はnを(1,2,9)にする - 負の序数は「末尾を超える大きな値」として扱う。0 に切り詰めたり、範囲外として無視したりはしない。そのため
n[-2,1]はn[1,末尾]と同じ範囲になる
n = (1,2,3,4)
i = n[-2,1]
n[-2,1] *= 5
i は (2,3,4)、n は (1,10,15,20) になる。先頭から数えたいときは n[0,1] のように 0 以上で書く。
範囲外の要素¶
範囲外の要素を読むと、簡易配列でも汎用配列でも、未定義の変数と同じく VOID になる(Tc574-8 / Tc603-1以降)。範囲指定の読み出しが範囲外のときは、空の範囲(簡易配列なら空文字列、汎用配列なら空の配列)になる。
_a = (1, 2)
_a[9] // VOID
_a[9] == "" // 1
_a[9] == 0 // 1
_a[9] + 1 // 1 (整数)
GETTYPE(_a[9]) // 0
_a[5, 6] // 空の配列
"a,b"[5, 6] // ""
- VOID は文字列としては空文字列、数値としては 0 として扱われる。
== ""やif _a[9]の結果は、空文字列だったときと変わらない - 関数の最後の行などで範囲外の要素をそのまま書いても、出力の候補にはならない(未定義の変数と同じ。空文字列だったときは空文字列が候補になっていた)
- 数値に
[ ]を付けたとき(5[1])は、要素が1つの配列とみなす。0 番目はその数値、それ以外は VOID - 末尾より先の要素へ代入すると(
_a[9] = 1)、間は VOID で埋められる。埋める個数の上限は 16,777,216 個で、超える代入(_a[2000000000] = 1など)はメモリ不足として扱われ、E0100 で実行が中断されてshiori.OnMemoryErrorが呼ばれる(Tc603-4以降。それまでは上限が無く、メモリを使い尽くすことがあった) - 範囲外の要素への複合代入(
_a[9] += 1)も VOID として計算するので、整数の 1 になる(600 系では Tc602-10 から。500 系では Tc574-8 から) - Tc574-7 / Tc602-13 までは、汎用配列の範囲外は空文字列、簡易配列の範囲外は VOID と、配列の種類で結果が違っていた。そのため
_a[9] == 0は偽、_a[9] + 1は文字列"1"、GETTYPE(_a[9])は 3 だった。簡易配列の範囲指定が範囲外のときは VOID、数値の範囲指定が範囲外のときも VOID だった
並列出力¶
すべての式 / 値の前に parallel を書ける。parallel は、出力候補の値が汎用配列だった場合に、要素のそれぞれを出力候補の値にする機能を持つ。
foo0
{
("A","B","C")
"地球"
}
foo1
{
parallel ("A","B","C")
"地球"
}
foo0 の出力は、("A", "B", "C") もしくは "地球"。foo1 の出力は、"A"、"B"、"C"、"地球" のいずれかになる。
汎用配列以外に parallel を使っても意味がなく、書かないのと同じ。たとえば次の 2 つの記述は等価。
parallel STRLEN("earth")
STRLEN("earth")
選択方式の array と parallel を利用すると、関数の出力候補と汎用配列を相互に変換できる。さまざまな応用が考えられる。たとえば次の非常に簡潔な関数 cyclic は、汎用配列から記述順に要素値を取り出す。
cyclic : sequential
{
parallel _argv
}
逆に、すべての式 / 値の前に void と書くことで、計算結果を破棄し、出力候補にしないこともできる。