コンテンツにスキップ

値の入れ子と多次元代入(Tc600-3以降)

Tc600-3 以降では、配列の要素、ハッシュの値、ハッシュのキーに、配列やハッシュを入れられる(入れ子)。入れ子の内側は [ ] を続けて書いて読み書きする。要素の要素へ直接代入することを多次元代入と呼ぶ。

500 系では配列の要素はスカラーだけで、a[x][y] = v は読み込み時にエラー E0029 だった。500 系からの変更点は 600 での変更点 を参照。

ハッシュそのものについては ハッシュ を参照。

入れ子の作り方

ハッシュの値に入れる

ハッシュの要素に代入すれば、配列もハッシュもそのまま値になる。

_h = IHASH()
_h["list"] = (1, 2, 3)        // 値に配列
_h["sub"] = IHASH("x", 1)     // 値にハッシュ

IHASH の引数に配列を書くと、ほかの関数と同じく展開される。IHASH("list", (1, 2, 3)) は IHASH("list", 1, 2, 3) と同じになり、値に配列は入らない。配列を値にしたいときは、上のようにあとから代入する。ハッシュは展開されないので、IHASH("sub", IHASH("x", 1)) のように書ける。

配列の要素にハッシュを入れる

ハッシュは , 演算子でも要素への代入でも展開されず、1つの要素になる。

_h = IHASH("x", 1)
_a = (0, _h, 2)     // 要素数 3。_a[1] はハッシュ
_a[2] = _h          // 同上
_a ,= _h            // 末尾に追加

配列の要素に配列を入れる

配列の中に新しく配列を入れる構文は無い。, 演算子、,=、要素への代入(_a[1] = (8, 9))のどれも、右辺の配列を展開して差し込む(500 系と同じ)。

_a = (1, 2)
_a[1] = (8, 9)      // 1,8,9 (要素数 3)
_a[1] = IARRAY()    // 1,9 (空の配列を差し込むと要素が消える)

ただし、代入先の要素がすでに配列やハッシュのときは、展開せずに丸ごと置き換える(Tc603-1以降。内側の配列やハッシュを丸ごと書き換える)。

配列の中に配列を持つ値は、次のような関数が返す。

_v = PARSEJSON('[[1,2,3],[4]]')   // 要素数 2。_v[0] は配列 (1,2,3)

こうして得た配列の中の配列も、多次元代入で書き換えられる(Tc602-7以降。途中の値ごとの動作)。

段階的に組み立てる

内側のハッシュは自動では作られない。途中のハッシュは IHASH で作ってから、その中へ代入する。

_m = IHASH()
_m["a"] = IHASH()
_m["a"]["b"] = 1
_m["a"]["list"] = IARRAY()
_m["a"]["list"] ,= "p"
_m["a"]["list"] ,= "q"
DUMPJSON(_m)   // {"a":{"b":1,"list":["p","q"]}}

途中のハッシュを作らずに _m["c"]["d"] = 1 と書くと、_m["c"] はハッシュではなく配列 (1) になる(途中の値ごとの動作)。

読み出し

[ ] を続けて書くと、1つ目の [ ] の結果に2つ目の [ ] を、その結果に3つ目を……と順に適用する。

_h = IHASH()
_h["list"] = (1, 2, 3)
_h["sub"] = IHASH("x", 1)
_a = (0, _h, 2)

_h["sub"]["x"]      // 1
_h["list"][1]       // 2
_a[1]["sub"]["x"]   // 1
_a[1]["list"]       // (1,2,3) そのものが返る

それぞれの [ ] は、その時点の値の型に応じて次のように動く。どの段でも、該当する要素が無いときは未定義の変数と同じく VOID になり、そこから先の [ ] も VOID になる(エラーにはならない。範囲外の要素)。

値 [x] の結果
配列 x 番目の要素。要素が配列やハッシュならそのまま返る
ハッシュ キー x の値(キーの一致)
文字列 簡易配列として x 番目の要素
数値 x が 0 ならその値、それ以外は VOID
VOID(未定義の変数など) VOID

Tc602-13 までは、配列の範囲外、ハッシュの無いキー、VOID への [ ] は空文字列だった(文字列と数値の範囲外はもとから VOID)。

範囲指定も使える。配列の範囲指定 [a, b] の結果は、入れ子の要素をそのまま含む配列になる。範囲外なら空の配列になる。ハッシュの範囲指定は空文字列になる。

_v = PARSEJSON('[[1,2],[3,4],[5,6]]')
_v[0, 1]     // 要素数 2 の配列。どちらの要素も配列のまま

簡易配列のデリミタ

SETDELIM で変数に設定したデリミタは、その変数に直接付けた1段目の [ ] にだけ効く。入れ子の中の文字列を簡易配列として読むときは、2段目以降の [ ] でデリミタを指定する。

_s = "a|b"
SETDELIM(_s, "|")
_t = (_s, "c")
_t[0][1]         // VOID (2段目は "," で区切られるので範囲外)
_t[0][1, "|"]    // "b"

文字列への埋め込み

文字列の中でも [ ] を続けて書ける。

"値は %_h['sub']['x'] です"      // 値は 1 です
"値は %(_h['list'][1]) です"     // 値は 2 です

入れ子の配列やハッシュそのものを埋め込むと、文字列化した結果になる。

関数の引数と , 演算子

関数の引数や , 演算子で展開されるのはいちばん外側の配列だけで、その要素にある配列やハッシュは展開されずに1つの要素のまま残る。ハッシュはいちばん外側でも展開されない。

_v = PARSEJSON('[[1,2,3],[4]]')

func(_v)        // _argc は 2。_argv[0] は配列 (1,2,3)
func(_v[0])     // _argc は 3。_argv は 1,2,3
func(_v, 9)     // _argc は 3。_argv[0] は配列 (1,2,3)
_c = (_v, 9)    // 要素数 3。_c[0] は配列 (1,2,3)

ARRAYSIZE(_v)      // 2
ARRAYSIZE(_v[0])   // 3

関数に入れ子の配列を渡すと、いちばん外側の配列は _argv そのものになる。受け取った側で「引数が1つの配列」と「引数が複数」を区別する手段は無いので、入れ子の配列を関数に渡すときは、ハッシュに包むか、配列の要素を1つずつ渡すとよい。

GETTYPE に要素が1つだけの配列を渡すと、配列ではなくその要素の型を返す(500 系と同じ)。上の例では GETTYPE(_v[1]) は 4 ではなく 1 になる。

foreach

入れ子の配列やハッシュも foreach で回せる。ループ変数には要素がそのまま入るので、要素が配列やハッシュなら、さらに foreach で回したり [ ] で読んだりできる。

_h = PARSEJSON('{"a":{"x":1,"y":2},"b":{"x":3}}')
foreach _h ; _k, _sub {
  foreach _sub ; _k2, _v {
    // _k/_k2 = _v の順に a/x=1, a/y=2, b/x=3
  }
}

foreach _h["a"] ; _k, _v { ... }   // 内側だけを回す

2変数の foreach は ハッシュ と フロー制御 を参照。

値のコピー

代入や引数渡しは値のコピーになる。入れ子の内側も含めて、コピー元とコピー先は互いに影響しない。

_h = IHASH("a", 1)
_h["self"] = _h
_h["self"]["a"] = 2
_h["a"]             // 1 のまま

内側を取り出して書き換えても、元の値は変わらない。元の値を変えたいときは、書き換えたものを代入し直すか、多次元代入を使う。

_sub = _h["self"]
_sub["a"] = 3        // _h["self"]["a"] は 2 のまま
_h["self"] = _sub    // これで _h["self"]["a"] が 3 になる

比較

配列同士、ハッシュ同士の == は中身まで比べる。入れ子の内側も同じ規則で再帰的に比べる。

  • 配列同士: 要素数が同じで、各要素が順に等しければ真
  • ハッシュ同士: 要素数、キー、値がすべて一致すれば真(値としての振る舞い)
PARSEJSON('{"a":[1,2]}') == PARSEJSON('{"a":[1,2]}')   // 1

ASEARCH で要素がハッシュの配列を探すときも同じ規則で比べる。ただし、探す値に配列を渡すと引数として展開されるので、配列そのものを探すことはできない。

ASORT は、要素の配列やハッシュを文字列にして(int / double では 0 として)並べる(要素が配列やハッシュのとき)。

多次元代入

要素の要素へ直接代入できる。= のほか += ,= などすべての代入演算子で使える。

_h["sub"]["x"] = 5
_h["list"][0] += 10
_h["list"] ,= 4               // 内側の配列の末尾に追加
_a[1]["sub"]["y"] = "new"

右辺の値は、1次元の代入と同じく最後の [ ] の位置に入る。そのため、その位置の要素がスカラーなら、右辺の配列は展開して差し込まれる。内側の配列を丸ごと置き換えるときは、1つ手前の段に代入する。

_h["list"] = (1, 2, 3)
_h["list"][0] = (7, 8)    // _h["list"] は 7,8,2,3
_h["list"] = (7, 8)       // _h["list"] を (7,8) に置き換える

範囲指定の代入(_h["list"][0, 1] = "R")も1次元と同じように使える。

内側の配列やハッシュを丸ごと書き換える

範囲指定でない代入で、代入先の要素が今配列かハッシュのときは、右辺を展開せずに要素を丸ごと置き換える(Tc603-1以降)。配列の中の配列にも、1つ手前の段への代入と同じ書き方で、置き換え・追加・要素ごとの計算ができる。

_v = PARSEJSON('[[1,2],[3,4]]')
_v[0] = (7, 8, 9)     // _v は (7,8,9),(3,4)   丸ごと置き換える
_v[0] ,= 5            // _v は (7,8,9,5),(3,4) 内側の末尾に追加する
_v[0] += 1            // _v は (8,9,10,6),(3,4) 内側の要素ごとに計算する
_v[0] = _v[0]         // 変わらない
_v[0] = IARRAY        // _v は (),(3,4)        空の配列に置き換える(要素は消えない)
_v[0, 0] = IARRAY     // _v は (3,4)           範囲指定なら展開して差し込む(要素を消す)
  • 代入先の要素がスカラーのとき、範囲外(まだ無い)のときは、これまでどおり右辺の配列を展開して差し込む(_a[1] = (8, 9) の挿入、_a[1] = IARRAY の削除)
  • 範囲指定の代入は、要素の型によらず展開して差し込む(Python のスライスへの代入と同じ)。内側の配列そのものを消すときは _v[0, 0] = IARRAY のように書く
  • 根がハッシュのとき(_h["list"] = ...)は、もとから置き換えになる
  • 500 系には入れ子の配列が無いので、500 系の辞書の動作は変わらない

Tc602-13 までは、根が配列のときに内側の配列が展開されて、入れ子が崩れていた(_v[0] = _v[0] で _v が 1、2、(3,4) の3要素、_v[0] ,= 5 で 1、2、5、(3,4) の4要素になる)。

途中の値ごとの動作

多次元代入は、最後の段から根元の変数に向かって、それぞれの段の値を書き換えては1つ手前の段へ入れ直す。それぞれの段の値の型によって、次のように動く。

途中の値 動作
ハッシュ キーの値を追加・上書きする
配列 要素を書き換える。範囲外なら、足りない分を VOID で埋めて末尾に追加する
文字列 簡易配列として書き換える
数値 配列に変えてから書き換える。0 番目は VOID になり、元の数値は失われる
無い(範囲外・キーが無い) 未定義の変数と同じく、汎用配列を作って書き換える
_a = ("p,q", 1)
_a[0][1] = "Q"        // _a[0] は "p,Q"

_h = IHASH("l", (1, 2))
_h["l"][4] = 9        // _h["l"] は 1,2,VOID,VOID,9

_n = (1, 2.5)
_n[1][2] = 9          // _n[1] は配列 (VOID,VOID,9)

_e = IHASH()
_e["n"]["x"] = 5      // _e["n"] は配列 (5) (ハッシュは作られない)
_e["m"][2] = 5        // _e["m"] は配列 (VOID,VOID,5)

_e["n"]["x"] = 5 で _e["n"] が (5) になるのは、キー "x" を配列の序数として読むと 0 番目になるため。未定義の変数への _x["x"] = 5 と同じ結果になる。

Tc602-13 までは、無い段を空文字列の簡易配列として書き換えていたので、_e["n"] は文字列 "5"、_e["m"] は文字列 ",,5" になっていた。

書き換えた値を1つ手前の段へ入れ直すときは、その値が配列でも展開せず、1つの要素として置き換える。右辺の配列が展開されるのは最後の段だけなので、配列の中の配列も入れ子のまま書き換えられる。

_v = PARSEJSON('[[1,2],[3,4]]')
_v[0][0] = "A"        // _v は ("A",2),(3,4)
_v[1][0] = (7, 8)     // _v は ("A",2),(7,8,4)

ただし、途中の段が範囲指定のとき(_v[0, 1][0] = 9)は、取り出した範囲の配列を書き換えてから、展開して元の範囲へ差し戻す。

Tc602-6 までは、配列の中の配列に多次元代入すると、書き換えた内側の配列が外側の配列へ展開されて入れ子が崩れていた(上の _v[0][0] = "A" で _v が "A"、2、(3,4) の3要素になる)。途中の値が数値の場合も、作った配列が外側へ展開されていた。参照渡し(&_v[0][0])の書き戻しも同じ。

代入の根元

代入の根元は変数でなければならない。(式)[0] = 1 や func()[0] = 1 などはエラー E0029 になる。

参照渡し

フィードバック演算子(参照渡し)でも要素の要素を渡せる(Tc600-4以降)。func(&_h["sub"]["x"]) の中で _argv[0] を書き換えると _h["sub"]["x"] が変わる。Tc600-3 では手前の次元が無視され、_h["x"] に書き込まれていた。

文字列化と保存

入れ子の値を文字列にすると(TOSTR、文字列との連結、埋め込みなど)、内側もそのまま , や = でつながれるので、どこまでが内側なのかわからなくなる。

_h = IHASH()
_h["l"] = (1, 2)
_h["m"] = IHASH("x", 1)
TOSTR(_h)    // "l=1,2,m=x=1"

入れ子の形を保ったまま扱いたいときは次の方法を使う。

目的 方法
変数をファイルに保存して復元する SAVEVAR / RESTOREVAR。入れ子は IARRAY{...} / IHASH{...} の形で保存される(保存形式)
中身を確認する DUMPVAR(Tc600-4以降)。入れ子は (array)[ ... ] / (hash){ ... } で表示される。LOGGING(Tc602-8以降)も、入れ子の配列を ( )、ハッシュを { } で囲んで書き出す
文字列にして受け渡す DUMPJSON と PARSEJSON。ただし JSON で表せない区別(整数のキーと文字列のキー、VOID と空文字列など)は失われる

制限

  • キーが配列のハッシュは、h[キー] の序数の第1要素がキーになるため、配列をキーにして引くことはできない(foreach や HASH_KEYS では取り出せる)。キーがハッシュなら h[キー] で引ける

関連