NScripterの深淵

NScripter/NSLua/Lua5.1/関連プログラム

▼メニュー

ゴブリンでもわかるNSLua/Lua5.1 第4回:Lua5.1入門 連想配列(テーブル)

プログラミングにおける配列

「複数の変数を一元化して管理できたら楽じゃね?」という需要に応えた概念。

ご存知の通り、通常の変数はしばしば「箱」に喩えられる。値を格納するXという名前の箱!

そして一次元配列は「名札付きのロッカー」に喩えられる。Xという名前のロッカーの、上から3番目の/Aという名前の引き出し!

二次元以上の配列は「住所」に喩えられることが多い。"1"丁目"2"番地"3"号棟の建物!

人類と二次元配列

人間と二次元配列の関わりはかなり古くから存在し、たとえば平安京なんかも二次元配列的([南北][東西])に座標を表していた。

同様に、将棋やチェスの盤面も二次元配列([縦][横])で表される。

中学数学で習った一次関数のグラフ(X,Y)も、その気になれば二次元配列の添え字(キー)として解釈可能である。

もっと身近なところでは、マンションやホテルの部屋番号なども基本的に[階層][部屋番号]の二次元配列で表される。

国内における固定電話の番号も[市外局番][個人の番号]で二次元配列。

意識しようとしまいと、我々人類の日常生活は二次元配列で溢れている。どうだ参ったか。

ゲーム用途では

状態異常のフラグを管理したり、キャラクター単位でステータスを管理したり、イベントクリアフラグの類を一元管理したり、二次元配列で2Dダンジョンのマップデータを管理したり、色んな箇所で用いられている。

NScripterにおける配列

実のところ日頃使っている数値/文字列変数も「"数値/文字列変数"という名前の、長さ4096(変更可能)の一次元配列」として解釈可能である。(もちろん、NScripterユーザーがそのように意識する機会はおそらくほとんど無い)

それとは別にNScripterにも配列変数と呼ばれる概念は存在するものの、制限がとても多い。

*define
;配列変数の宣言
dim ?0[10]
game

*start
;配列変数への代入はmov/movlのみ有効
mov ?0[9],100
;参照だけなら可能だが
add %0,?0[0]
;mov(l)以外の命令で代入が発生するとエラー。不便だね
add ?0[9],1 ;エラー!

;値も添え字も文字列は扱えない。不便だね
mov ?0[9],"あいうえお" ;エラー!
mov ?0["あいうえお"],123 ;エラー!
;こうすることはできる
;numalias number,%0
;mov ?0[number],123
;
;でも、代わりにこの手の記述を用いる方がよほど融通がきく
mov %0,101
mov %%0,10

;なお、セーブデータにNスク配列は含まれない

「mov(l)以外の命令で値を代入できない」という制限が極端に重く、非推奨命令スレスレである。そしてLuaからNスク配列変数へのアクセス手段も無い。なぜか?

Luaにおける配列(テーブル)

Luaにはとても高機能な配列が用意されており、本体配列を丸ごと置き換える手間に足る利便性を有している。

キー(添え字)に文字列等を使える、(関数等と同じく)その場で宣言して良いなど多くの利点を持つ。ひとまずは「だいぶ高機能だなあ」程度の理解で良いのでざっと眺めていこう。

--テーブルを初期化、代入する
local tab={}

--キー(添え字)も値も型の制限は無い。
tab[1]=123
tab["あいうえお"]="かきくけこ"

--当然関数も代入できる。
tab["hoge"]=function(aaa)
        return aaa*3
    end
local num=tab["hoge"](10) -->local num=30
--なんならキーにも使えるが実用性は薄い

--テーブルの値にテーブルを代入してもよい。
--つまり入れ子(多次元)にできる。
tab["hoge"]={}
tab["hoge"]["fuga"]=123

--初期化時に値を代入しても構わない
local tab2={"あ","い","う",{},[5]=100,["キー"]="値",}
-->tab2[1]="あ"
-->tab2[2]="い"
-->tab2[3]="う"
-->tab2[4]={}
-->tab2[5]=100
-->tab2["キー"]="値"

--初期化時に値を並べる場合、
--末尾の「,」はあっても無くてもよい
local tab3={1,2,3,}
local tab4={1,2,3}
--後からコードをいじる際に前者だとミスが減る

local tab5={
    --記述時に改行してもよい
    --というか、
    --基本的にスペースやタブや空行はどこに入れてもよい 
    --(Luaが内部で勝手に整理してくれる)
    --なるべく人間にとって読みやすい書き方を心掛けよう
    --Visual Studio Codeのようなエディタを使うと、
    --ある程度勝手に整形してくれて楽なのでオススメ
    ["hp"]=100,
    ["mp"]=100,
}

--キーの指定に変数を使ってもよい
local tab6={}
local str="abc"
tab6[str]="string" -->tab6["abc"]="string"

Luaの世界では「テーブル」、プログラミング一般でいうと「連想配列」または「辞書配列」と呼ばれる(名前は例によってどうでもいい)。

これまでに登場した「数値型」「文字列型」「関数型」のように、「テーブル型」として概ね等しい扱いになっている。テーブル操作用の関数もいくつか定義されており、それらについては標準ライブラリの紹介時に触れる。

テーブルの参照とコピー

--数値や文字列は代入時にコピーされていた
local str1="あいうえお"
local str2=str1
str2=""
alert(str1)-->"あいうえお"

--テーブルは他の変数に「代入」してもコピーされない
--(同一のテーブルを複数の変数が共有・参照する)
local tab1={1,2,3}
local tab2=tab1
tab2[1]=999
alert(tab1[1])-->999

①最初にテーブルが定義された時点でメモリ上に「本体」が出現する

②以降の代入時、変数は①を参照(アクセス)する

③参照する変数が一個もなくなったテーブルはメモリから自動で消える

こうなっている。巨大な配列を毎回コピーしているとメモリがいくらあっても足りないがゆえ。

(実は関数の代入も同じ仕様だが今は気にしなくてもいい)

「じゃあテーブルをコピーしたい時はどうすんの?」という疑問が湧くかもしれない。

--こうする
local tab1={1,2,3}
loca; tab2={}
for k,v in pairs(tab1) do
    --kにキー(添え字)、vに値が代入される
    alert(k,v)
    tab2[k]=v
end

--「pairs関数」という仕掛けが用意されている。
--内部的には若干ややこしい理屈で動いているので、
--「上記のようにforを書くとキー=値のペアを全部回してくれる」
--とだけ覚えておいてほしい。

頻繁にコピーしたい場合はそういう関数を作ってしまうのが常套手段である。そうした需要が出てくるのは(たぶん)ある程度習熟した後なので、今は忘れてもよい。

Luaの「1オリジン」

そういえば、LuaNScripterと異なり「1」が配列等における数字の基準値(オリジン)となっている。

local tab={}
--ここが先頭
tab[1]=123

--0未満の数値もキーとしては使用できるが、テーブルの「長さ」には含まれない
tab[0]=123
tab[-1]=123 
tab[3.14]=123 

--配列の長さは「#配列名」で取得できる
alert(#tab)
-->alert(1)

--1以降の自然数について、「中身が入っている」キーが
--n個続いていた場合、長さnとなる
tab={1,2,3,4,5} -->#tab==5
tab={[1]=30,[3]=50} -->#tab==1
tab={[10]="",["hoge"]=50} -->#tab==0

テーブルに限った話ではない。NScripterは「0バイト目」から数えるが、Luaは「1文字目」から数える…といった具合に、この基準の差はプログラム全体に及ぶ。

「1オリジン」はプログラミング言語としてはちょっと珍しい(大抵はNScripterと同じく0オリジン)。

慣れれば非常に直感的なのだが、NScripter含む他のプログラミング言語に慣れていると最初は戸惑うかもしれない。

まとめ

テーブルはLuaの中で大きな地位を占めており(実は標準ライブラリの関数も全て巨大なテーブルに入っている)、隅々まで紹介すると初心者向け講座ではなくなってしまう。

「今はまだ覚えなくてよい」だらけになってしまったが、「NScripter本体の配列よりだいぶ高機能っぽい」とさえ分かれば導入としては十分だろう。細部は必要になってから覚えればよい。

次回はNSLua関数(のうちNScripterと値をやり取りするもの)に触れながら、標準ライブラリも紹介してゆく。

--%0の値を取得
local num=NSGetIntValue(0)
--%0に値を代入
NSSetIntValue(0,num)
--$0の値を取得
local str=NSGetStrValue(0)
--$0に値を代入
NSSetStrValue(0,str)