コンテンツにスキップ

Lua/テーブル

出典: フリー教科書『ウィキブックス(Wikibooks)』
< Lua

テーブル(Tables)はLuaの唯一のデータ構造ですが、他の多くの言語のデータ構造よりも柔軟性に富んでいます。辞書(辞書の定義が用語に対応するように、インデックスに対応する値を作るため)、連想配列(インデックスに値を関連付けるため、インデックスに関連付けられた値の配列になる)、ハッシュテーブル、シンボルテーブル、ハッシュマップ、マップとも呼ばれる。これらの配列はテーブルのコンストラクタで作成します。テーブルのコンストラクタは2つの中括弧で定義し、オプションでカンマやセミコロンで区切られた値を含めることができます。次の例は、配列(値の順序付きリスト)の例で、配列の長さを求める方法を示しています。

local array = {5, "text", {"more text", 463, "even more text"}, "more text"}
print(#array) --> 4 ; 文字列に対する # 演算子は配列に対しても使用でき、その場合は配列内の値の数を返します。
--  この例では、テーブルを他のテーブルの中に入れ子にすることができることを説明しています。テーブル自体が値であるため、テーブルは他のテーブルを含むことができる。テーブルの配列は多次元配列と呼ばれる。

テーブルには、nilを除くあらゆる型の値を格納することができる。これは論理的なことで、nilは値がないことを表しています。表中に「値のないもの」を挿入しても意味がない。表の中の値はカンマかセミコロンで区切ることができ、何行にもわたって続くことができます。1行の表コンストラクタにはカンマを、複数行の表にはセミコロンを使うのが一般的ですが、これは必須ではありません。

local array = {
	"text";
	{
		"more text, in a nested table";
		1432
	};
	true -- 論理値もまたテーブルに含めることが出来ます。
}

テーブルはフィールドと呼ばれる値の組で構成され、一方はインデックス(キーとも呼ばれる)、他方はそのインデックスに対応する値である。配列の場合、インデックスには常に数値が使われる。辞書の場合は、インデックスに任意の値を指定することができます。

local dictionary = {
	"text";
	text = "more text";
	654;
	[8] = {}; -- an empty table
	func = function() print("tables can even contain functions!") end;
	["1213"] = 1213
}

上の例で示したように、辞書に値を追加する際には、配列のように値だけを追加する方法(この場合、インデックスは数値になる)、識別子と等号を値の前に付ける方法(この場合、インデックスは識別子に対応する文字列になる)、値を括弧で囲んで等号を値の前に付ける方法(この場合、インデックスは括弧の中の値になる)がある。後者は,どんな値や式にも対応できるため,インデックスを値に対応させる最も柔軟な方法です。

テーブルの値を評価する式の後に、その値が対応するインデックスを大括弧で囲むことで、テーブル内の値にアクセスすることができます。

local dictionary = {number = 6}
print(dictionary["number"]) --> 6

インデックスがLuaの識別子としての条件を満たす文字列(スペースを含まない、数字で始まらない、数字、文字、アンダースコア以外を含まない)であれば、文字列の前にドットを付けることで、ブラケットなしでアクセスすることが可能です。

local dictionary = {["number"] = 6}
print(dictionary.number) --> 6

前の2つの例では、同じテーブルを作成して同じ値を表示していますが、異なる記法でインデックスを定義してアクセスしています。また、他のテーブルを含むテーブルの場合、まず最初のテーブルにインデックスを付けて入れ子テーブルを取得し、次に入れ子テーブルの中の値にインデックスを付けて、入れ子テーブルの中の値にアクセスすることも可能である。

local nested_table = {
	[6] = {
		func = function() print("6") end
	}
}
nested_table[6].func() -- これは、ネストされたテーブルにアクセスし、その中にある関数にアクセスし、その関数を呼び出して、数字6を表示します。

foreach ループ

[編集]

文の章では、条件制御付きループカウント制御ループの2種類のループを説明しました。Luaには、generic for loopとも呼ばれるforeachループという3つ目のタイプのループが存在します。foreachループは、テーブル内の各フィールドに対してコードを実行するループです。以下の例では、数値の配列の項目を走査し、すべてのインデックスとそれに対応する値の合計を数字1で表示するforeachループを実演しています。

local array = {5, 2, 6, 3, 6}

for index, value in next, array do
	print(index .. ": " .. value + 1)
end
-- Output:
-- 1: 6
-- 2: 3
-- 3: 7
-- 4: 4
-- 5: 7

次の例の2つのループは、前の例のループと同じことをすることになります。

local array = {5, 2, 6, 3, 6}

for index, value in pairs(array) do
	print(index .. ": " .. value + 1)
end

for index, value in ipairs(array) do
	print(index .. ": " .. value + 1)
end

最初の例に示されているメソッドは、前の例の最初のループと同じことを行います。 ただし、最後の1つ( ipairs 関数を使用するもの)は、テーブルにない最初の整数キーまでしか反復しません。つまり、順番に並んでいる数値のみを反復処理します。 配列内にあります。 以前はtable.foreachtable.foreachiという2つの関数がありましたが、Lua 5.1では非推奨になり、Lua5.2では削除されました。 非推奨は、機能またはプラクティスに適用されるステータスであり、削除または置き換えられたため、回避する必要があることを示します。 したがって、foreachループを使用してテーブルをトラバースすることは、これら2つの関数を使用するよりも望ましい方法です。

テーブルのアンパック

[編集]

Luaでは、単純な値のリストであるタプルと、インデックスを値にマッピングするデータ構造であるテーブルが区別されています。多くの値を返す関数の呼び出しは、タプルに評価される。一度に多くの変数が代入される代入文の値のリストもタプルである。可変引数関数で使用されるvararg式もタプルです。タプルは値のリストであり、単一の値ではないので、多くの変数に格納することはできますが、変数に格納することはできません。タプルを評価する式をテーブル・コンストラクターに入れることで、タプルを配列に変換することは可能です。

function return_tuple()
	return 5, 6 -- この関数は2つの値を返すので、この関数を呼び出すとタプルに評価されます。
end

local array = {return_tuple()} -- local array = {5, 6} と同じ
local a, b = return_tuple() -- local a, b = 5, 6 と同じ
print(return_tuple()) -- print(5, 6) と同じ

テーブルライブラリーのunpack関数に配列を引数として与えることで、配列をアンパック(テーブルからタプルに変更)することが可能である。

local array = {7, 4, 2}
local a, b, c = table.unpack(array)
print(a, b, c) --> 7, 4, 2

Lua 5.2以前のバージョンでは、unpack関数は基本ライブラリに含まれていました。その後、テーブルライブラリに移されました。

メソッド

[編集]

テーブルは関数を格納し、その関数に名前を関連付けることができるため、ライブラリを作成する際によく使用されます。また、Luaにはメソッドというオブジェクトを操作するための関数を作成するために使用できる構文糖があり、一般的にはテーブルで表現されることになります。これは、本書の範囲外であるオブジェクト指向プログラミングを知らない人には少しわかりにくいかもしれませんので、趣旨を理解できなくても問題はないでしょう。下の2つの例も全く同じことをしています。

local object = {}

function object.func(self, arg1, arg2)
	print(arg1, arg2)
end

object.func(object, 1, 2) --> 1, 2
local object = {}

function object:func(arg1, arg2)
	print(arg1, arg2)
end

object:func(1, 2) --> 1, 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関数を使用してランダムに生成されます。

local associative_array = {
	defined_field = "this field is defined"
}

setmetatable(associative_array, {
	__index = function(self, index)
		return math.random(10)
	end
})

上記の例で気付くかもしれないことがたくさんあります。気付くかもしれないことの1つは、indexメタメソッドを含むフィールドの名前の前に2つのアンダースコアが付いていることです。これは常に当てはまります。Luaがテーブルのメタテーブルでメタメソッドを探すとき、メタメソッドの名前に対応し、2つのアンダースコアで始まるインデックスを探します。気付くかもしれないもう一つのことは、indexメタメソッドが実際には関数であり(ほとんどのメタメソッドは関数ですが、すべてがそうであるわけではありません)、2つの引数を取ります。最初の引数selfは、indexメタメソッドが呼び出されたテーブルです。この場合、associative_array変数を直接参照することもできますが、これは、単一のメタテーブルが多くのテーブルに使用される場合に役立ちます。 2番目の引数は、インデックス付けが試行されたインデックスです。最後に、この関数は、値を返すことによってテーブルにインデックスを付けたコードに何を与えるべきかをLua仮想マシンに指示することに注意してください。ほとんどのメタメソッドは関数のみにすることができますが、indexメタメソッドもテーブルにすることができます。それがテーブルの場合、Luaは、プログラムが存在しないテーブルのフィールドにインデックスを付けようとすると、そのテーブルで同じインデックスを探します。見つかった場合は、indexメタメソッドで指定されたテーブル内のそのインデックスに対応する値を返します。

newindex(self, index, value)
newindex メタメソッドを使用すると、プログラムがテーブルに新しいフィールドを追加しようとしたときに、Luaバーチャルマシンに何をすべきかを指示することができます。このメソッドは関数であり、プログラムが書き込もうとするインデックスを持つフィールドがまだ存在しない場合にのみ呼び出されます。最初の 2 つは index メタメソッドの引数と同じもので、 3 番目はプログラムがフィールドの値を設定しようとしたときの値です。
concat(self, value)
concat メタメソッドを使用すると、プログラムがテーブルに値を連結しようとしたときに、連結演算子(..)を使ってLuaバーチャルマシンに処理を指示することができます。
call(self, ...)
call メタメソッドを使用すると、プログラムがテーブルを呼び出そうとしたときに何が起こるかを指定することができます。これにより、テーブルをあたかも関数であるかのように振る舞うことができるようになります。テーブルがコールされたときに渡された引数は、いつものようにテーブル自身である self 引数の後に渡されます。このメタメソッドは値を返すために使うこともでき、その場合はテーブルを呼び出すとその値が返されます。
unm(self)
このメタメソッドは、単項のマイナス演算子を使用したときの効果を表で指定するために使用します。
tostring(self)
このメタメソッドには、テーブルを引数として tostring 関数が呼ばれたときに返すべき値を返す関数を指定することができます。
add(self, value)
sub(self, value)
mul(self, value)
div(self, value)
mod(self, value)
pow(self, value)
これらのメタメソッドを使用すると、テーブルに値が加算、減算、乗算、除算されたときにそれぞれ何をするかを仮想マシンに指示することができます。最後の 2 つは似ていますが、それぞれモジュラス演算子 (%) と指数演算子 (^) のためのものです。
eq(self, value)
このメタメソッドは、等値演算子(==)で使用されます。比較される2つの値が同じメタデータを持つ場合にのみ使用されます。非等価演算子(~=)は、この関数の結果の逆を使用します。
lt(self, value)
le(self, value)
これらのメタメソッドは、"less than" および "less than or equal to" 演算子で使用されます。greater than" および "greater than or equal to" 演算子は、これらのメタメソッドが返す値の逆を返します。これらの演算子は、値が同じメタデータを持つ場合にのみ使用されます。
gc(self)
このメタメソッドは、このメタメソッドを持つメタテーブルがアタッチされた値をガベージコレクタが収集する前に、Luaから呼び出されます。ユーザーデータの値に対してのみ機能し、テーブルに対しては使用できません。
len(self)
このメタメソッドは、ユーザーデータの値に長さ演算子(#)を使用する際にLuaから呼び出されます。テーブルでは使用できません。
metatable
このメタメソッドが存在する場合(何でもかまいません)、テーブルでgetmetatableを使用すると、メタテーブルの代わりにこのメタメソッドの値が返され、 setmetatable関数でテーブルのメタテーブルを変更することはできません。
mode
このメタメソッドには、"k" あるいは "v" (あるいはその両方) を含む文字列を指定する必要があります。文字 "k" がある場合、テーブルのキーは弱く(weak)なります。もし "v" という文字があれば、テーブルの値は弱くなります。これが何を意味するかは、ガベージコレクションと一緒に後で説明する。

メタテーブルは、通常のLuaプログラムではテーブルでのみ使用できますが、実際にはLuaが演算子と操作を処理する方法の中核となるメカニズムであり、理論的には任意の値で使用できます。 ただし、Luaでは、ドキュメント化されていないnewproxy関数で作成されたテーブルとuserdata値でのみ使用できます。 LuaのCAPIまたはデバッグライブラリを使用して、数値や文字列などの他のタイプの値のメタテーブルを設定することができます。

メタメソッドを呼び出さずにテーブルに対して操作を実行することが望ましい場合があります。 これは、インデックス作成、テーブルへのフィールドの追加、等しいかどうかのチェック、およびrawgetrawsetrawequalを使用したテーブルの長さの取得に使用できます。 それぞれ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 "というファイル中のすべての行を表示する。

for line in io.open("file.txt"):lines() do
	print(line)
end

イテレーターの作成

[編集]

イテレータは3つのものから構成されています。

  • 変換関数(transformation function)
  • 状態値(state value)
  • 1つまたは複数のループ変数(loop variables)

変換関数(transformation function)は、ループの各反復処理において、ループ変数(loop variables)(forとinの間に現れる変数)の値を変更するために使用されます。この関数は反復処理の前に呼び出され、最後の反復処理でループ変数が設定された値を引数として受け取ります。この関数は、これらの変数の新しい値を含むタプル(1つまたは複数の値)を返すことになっています。ループ変数は返されたタプルの構成要素に設定され、ループは反復されます。反復が完了すると(break文やreturn文で中断されていない限り)、変換関数が再び呼び出され、次の反復のためにループ変数が設定される別の値のセットが返されます、そして、その繰り返しです。変換関数の呼び出しとループの文の繰り返しのこのサイクルは、変換関数がnilを返すまで続けられます。

ループ変数と一緒に、変換関数にも状態値が渡されます。この状態値(state value)は、ループの間中、一定に保たれます。状態値は、例えば、変換関数が反復処理するデータ構造、ファイルハンドル、リソースへの参照を保持するために使用することができます。

以下に、10までの数列を生成する変換関数の例を示します。この変換関数は、valueに値が格納されるループ変数を1つだけ必要とします。

function seq(state, value)
	if (value >= 10) then
		return nil -- Returning nil will terminate the loop
	else
		local new_value = value + 1 -- The next value to use within the loop is the current value of `value` plus 1.
		-- This value will be used as the value of `value` the next time this function is called.
		return new_value
	end
end

ジェネリックforループは、変換関数、状態値、およびループ変数の初期値を含むタプルを想定しています。このタプルは、キーワードinの直後に含めることができます。

-- This will display the numbers from 1 to 10.
for value in seq, nil, 0 do
	print(value)
end

しかし、ほとんどの場合、このタプルは関数によって返されます。 このため、イテレーターファクトリー(iterator factories) を使用することができます。このファクトリーは、呼び出されると新しいイテレーターを返し、一般的な for ループで使用することができるようになります。

function count_to_ten()
	local function seq(state, value)
		if (value >= 10) then
			return nil
		else
			local new_value = value + 1
			return new_value
		end
	end
	return seq, nil, 0
end

for value in count_to_ten() do
	print(value)
end

Luaはクロージャと関数をファーストクラスのオブジェクトとしてサポートしているので、イテレーターファクトリーは変換関数内で使用できる引数も取ることができます。

function count_to(limit)
	local function seq(state, value)
		if (value >= limit) then
			return nil
		else
			local new_value = value + 1
			return new_value
		end
	end
	return seq, nil, 0
end

for value in count_to(10) do -- Print the numbers from 1 to 10
	print(value)
end

for value in count_to(20) do -- Print the numbers from 1 to 20
	print(value)
end


脚註

[編集]