Lua/テーブル

テーブル(Tables)はLuaの唯一のデータ構造ですが、他の多くの言語のデータ構造よりも柔軟性に富んでいます。辞書(辞書の定義が用語に対応するように、インデックスに対応する値を作るため)、連想配列(インデックスに値を関連付けるため、インデックスに関連付けられた値の配列になる)、ハッシュテーブル、シンボルテーブル、ハッシュマップ、マップとも呼ばれる。これらの配列はテーブルのコンストラクタで作成します。テーブルのコンストラクタは2つの中括弧で定義し、オプションでカンマやセミコロンで区切られた値を含めることができます。次の例は、配列(値の順序付きリスト)の例で、配列の長さを求める方法を示しています。
テーブルには、nilを除くあらゆる型の値を格納することができる。これは論理的なことで、nilは値がないことを表しています。表中に「値のないもの」を挿入しても意味がない。表の中の値はカンマかセミコロンで区切ることができ、何行にもわたって続くことができます。1行の表コンストラクタにはカンマを、複数行の表にはセミコロンを使うのが一般的ですが、これは必須ではありません。
テーブルはフィールドと呼ばれる値の組で構成され、一方はインデックス(キーとも呼ばれる)、他方はそのインデックスに対応する値である。配列の場合、インデックスには常に数値が使われる。辞書の場合は、インデックスに任意の値を指定することができます。
上の例で示したように、辞書に値を追加する際には、配列のように値だけを追加する方法(この場合、インデックスは数値になる)、識別子と等号を値の前に付ける方法(この場合、インデックスは識別子に対応する文字列になる)、値を括弧で囲んで等号を値の前に付ける方法(この場合、インデックスは括弧の中の値になる)がある。後者は,どんな値や式にも対応できるため,インデックスを値に対応させる最も柔軟な方法です。
テーブルの値を評価する式の後に、その値が対応するインデックスを大括弧で囲むことで、テーブル内の値にアクセスすることができます。
インデックスがLuaの識別子としての条件を満たす文字列(スペースを含まない、数字で始まらない、数字、文字、アンダースコア以外を含まない)であれば、文字列の前にドットを付けることで、ブラケットなしでアクセスすることが可能です。
前の2つの例では、同じテーブルを作成して同じ値を表示していますが、異なる記法でインデックスを定義してアクセスしています。また、他のテーブルを含むテーブルの場合、まず最初のテーブルにインデックスを付けて入れ子テーブルを取得し、次に入れ子テーブルの中の値にインデックスを付けて、入れ子テーブルの中の値にアクセスすることも可能である。
文の章では、条件制御付きループとカウント制御ループの2種類のループを説明しました。Luaには、generic for loopとも呼ばれるforeachループという3つ目のタイプのループが存在します。foreachループは、テーブル内の各フィールドに対してコードを実行するループです。以下の例では、数値の配列の項目を走査し、すべてのインデックスとそれに対応する値の合計を数字1で表示するforeachループを実演しています。
次の例の2つのループは、前の例のループと同じことをすることになります。
最初の例に示されているメソッドは、前の例の最初のループと同じことを行います。 ただし、最後の1つ( ipairs 関数を使用するもの)は、テーブルにない最初の整数キーまでしか反復しません。つまり、順番に並んでいる数値のみを反復処理します。 配列内にあります。 以前はtable.foreachとtable.foreachiという2つの関数がありましたが、Lua 5.1では非推奨になり、Lua5.2では削除されました。 非推奨は、機能またはプラクティスに適用されるステータスであり、削除または置き換えられたため、回避する必要があることを示します。 したがって、foreachループを使用してテーブルをトラバースすることは、これら2つの関数を使用するよりも望ましい方法です。
Luaでは、単純な値のリストであるタプルと、インデックスを値にマッピングするデータ構造であるテーブルが区別されています。多くの値を返す関数の呼び出しは、タプルに評価される。一度に多くの変数が代入される代入文の値のリストもタプルである。可変引数関数で使用されるvararg式もタプルです。タプルは値のリストであり、単一の値ではないので、多くの変数に格納することはできますが、変数に格納することはできません。タプルを評価する式をテーブル・コンストラクターに入れることで、タプルを配列に変換することは可能です。
テーブルライブラリーのunpack関数に配列を引数として与えることで、配列をアンパック(テーブルからタプルに変更)することが可能である。
Lua 5.2以前のバージョンでは、unpack関数は基本ライブラリに含まれていました。その後、テーブルライブラリに移されました。
テーブルは関数を格納し、その関数に名前を関連付けることができるため、ライブラリを作成する際によく使用されます。また、Luaにはメソッドというオブジェクトを操作するための関数を作成するために使用できる構文糖があり、一般的にはテーブルで表現されることになります。これは、本書の範囲外であるオブジェクト指向プログラミングを知らない人には少しわかりにくいかもしれませんので、趣旨を理解できなくても問題はないでしょう。下の2つの例も全く同じことをしています。
テーブル内の文字列インデックスに対応する関数を呼び出す場合、ドットの代わりにコロンを使用すると、テーブル自体である隠し引数が追加されます。同様に、ドットの代わりにコロンを使ってテーブル内の関数を定義すると、パラメータリストに隠れたパラメータ self が追加されます。関数の定義にコロンを使ったからといって、関数の呼び出しにコロンを使わなければならないわけではありませんし、その逆も同様です。
Luaでのテーブルのソートは比較的簡単です。ほとんどの場合、テーブルライブラリのsort関数を使用してテーブルを並べ替えることができるため、すべてが比較的簡単になります。sort関数は、配列の要素を指定された順序でインプレースで(つまり、新しい配列を作成せずに)並べ替えます。関数が2番目の引数として提供されている場合、その関数は配列の2つの要素を受け取り、最初の要素が最終的な順序で2番目の要素の前に来る必要があるときにtrueを返す必要があります。2番目の引数が指定されていない場合、Luaは<演算子に従って配列内の要素を並べ替えます。これは、2つの数値で使用され、最初の数値が2番目の数値よりも小さい場合にtrueを返しますが、文字列でも機能します。この場合、最初の文字列が辞書式順序で2番目の文字列よりも小さい場合にtrueを返します。
メタテーブル(Metatables)は、他のテーブルの動作を制御するために使用できるテーブルです。これは、メタメソッド(metamethods)を介して実行されます。これは、コードが特定の方法でテーブルを操作しようとしたときに何を実行する必要があるかをLua仮想マシンに示すテーブル内のフィールドです。メタメソッドはその名前で定義されます。たとえば、indexメタメソッドは、コードがまだ存在しないフィールドをテーブルにインデックス付けしようとしたときに何をすべきかをLuaに指示します。テーブルのメタテーブルは、setmetatable関数を使用して設定できます。この関数は、2つのテーブルを引数として受け入れます。最初のテーブルはメタテーブルを設定するテーブルであり、2番目のテーブルはテーブルのメタテーブルのメタテーブルです。設定する必要があります。テーブルのメタテーブルを返すgetmetatable関数もあります。次のコードは、メタテーブルを使用して、テーブル内に存在しないすべてのフィールドを、数値が含まれているように表示する方法を示しています。これらの数値は、math.random関数を使用してランダムに生成されます。
上記の例で気付くかもしれないことがたくさんあります。気付くかもしれないことの1つは、indexメタメソッドを含むフィールドの名前の前に2つのアンダースコアが付いていることです。これは常に当てはまります。Luaがテーブルのメタテーブルでメタメソッドを探すとき、メタメソッドの名前に対応し、2つのアンダースコアで始まるインデックスを探します。気付くかもしれないもう一つのことは、indexメタメソッドが実際には関数であり(ほとんどのメタメソッドは関数ですが、すべてがそうであるわけではありません)、2つの引数を取ります。最初の引数selfは、indexメタメソッドが呼び出されたテーブルです。この場合、associative_array変数を直接参照することもできますが、これは、単一のメタテーブルが多くのテーブルに使用される場合に役立ちます。 2番目の引数は、インデックス付けが試行されたインデックスです。最後に、この関数は、値を返すことによってテーブルにインデックスを付けたコードに何を与えるべきかをLua仮想マシンに指示することに注意してください。ほとんどのメタメソッドは関数のみにすることができますが、indexメタメソッドもテーブルにすることができます。それがテーブルの場合、Luaは、プログラムが存在しないテーブルのフィールドにインデックスを付けようとすると、そのテーブルで同じインデックスを探します。見つかった場合は、indexメタメソッドで指定されたテーブル内のそのインデックスに対応する値を返します。
メタテーブルは、通常のLuaプログラムではテーブルでのみ使用できますが、実際にはLuaが演算子と操作を処理する方法の中核となるメカニズムであり、理論的には任意の値で使用できます。 ただし、Luaでは、ドキュメント化されていないnewproxy関数で作成されたテーブルとuserdata値でのみ使用できます。 LuaのCAPIまたはデバッグライブラリを使用して、数値や文字列などの他のタイプの値のメタテーブルを設定することができます。
メタメソッドを呼び出さずにテーブルに対して操作を実行することが望ましい場合があります。 これは、インデックス作成、テーブルへのフィールドの追加、等しいかどうかのチェック、およびrawget、rawset、rawequalを使用したテーブルの長さの取得に使用できます。 それぞれrawlen関数。 最初の引数は、最初の引数で指定されたテーブルの2番目の引数として指定されたインデックスに対応する値を返します。 2番目は、3番目の引数で指定された値に、最初の引数で指定されたテーブルの2番目の引数で指定されたインデックスに対応する値を設定します。 3番目は、与えられた2つの値が等しいかどうかを返します。 最後に、4番目は、指定されたオブジェクトの長さ(整数)を返します。これは、テーブルまたは文字列である必要があります。
イテレーターは、foreachループと組み合わせて使用される関数です。ほとんどの場合、イテレーターはデータ構造を走査するために使用されます。例えば、pairs関数やipairs関数は、それぞれテーブルや配列の要素を走査するためのイテレーターとして使用されます。例えばpairs関数は、引数として与えられたテーブルと一緒にnext関数を返すので、pairs(dictionary)とin next, dictionaryの等価性が説明できます。
イテレーターはループ処理が必要なあらゆるケースに対応できるため、常にデータ構造で動作する必要はありません。例えば、file:lines関数は、反復処理ごとにファイルから1行を返すイテレーターを返します。同様に、code>string.gmatch関数は、文字列中のパターンにマッチしたものを反復処理ごとに返すイテレーターを返します。例えば、このコードは、"file.txt "というファイル中のすべての行を表示する。
イテレータは3つのものから構成されています。
変換関数(transformation function)は、ループの各反復処理において、ループ変数(loop variables)(forとinの間に現れる変数)の値を変更するために使用されます。この関数は反復処理の前に呼び出され、最後の反復処理でループ変数が設定された値を引数として受け取ります。この関数は、これらの変数の新しい値を含むタプル(1つまたは複数の値)を返すことになっています。ループ変数は返されたタプルの構成要素に設定され、ループは反復されます。反復が完了すると(break文やreturn文で中断されていない限り)、変換関数が再び呼び出され、次の反復のためにループ変数が設定される別の値のセットが返されます、そして、その繰り返しです。変換関数の呼び出しとループの文の繰り返しのこのサイクルは、変換関数がnilを返すまで続けられます。
ループ変数と一緒に、変換関数にも状態値が渡されます。この状態値(state value)は、ループの間中、一定に保たれます。状態値は、例えば、変換関数が反復処理するデータ構造、ファイルハンドル、リソースへの参照を保持するために使用することができます。
以下に、10までの数列を生成する変換関数の例を示します。この変換関数は、valueに値が格納されるループ変数を1つだけ必要とします。
ジェネリックforループは、変換関数、状態値、およびループ変数の初期値を含むタプルを想定しています。このタプルは、キーワードinの直後に含めることができます。
しかし、ほとんどの場合、このタプルは関数によって返されます。 このため、イテレーターファクトリー(iterator factories) を使用することができます。このファクトリーは、呼び出されると新しいイテレーターを返し、一般的な for ループで使用することができるようになります。
Luaはクロージャと関数をファーストクラスのオブジェクトとしてサポートしているので、イテレーターファクトリーは変換関数内で使用できる引数も取ることができます。