コンテンツにスキップ

配列

概要

演算子 [ ] は配列要素にアクセスするための演算子。配列には、文字列を分割して擬似的に配列と見なす簡易配列と、コンマ区切りで列挙された要素を扱う汎用配列の 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 と書くことで、計算結果を破棄し、出力候補にしないこともできる。