まず最初に、Lua テーブルが実際に何であるかを理解する必要があります。表面的には、それらは連想配列またはマップ(2 つの広く知られた CS 用語) として表示され、多くの場合、ハッシュ テーブルとして実装されます。これらは、いわゆるキー(一意) と値を関連付けるため、このように呼ばれます。これらは、キーによってインデックス付けされたキーと値のペアのコレクションとして表示できます。つまり、(一意の) キーを使用して対応する値を非常に迅速に見つけることができるように実装されています。
Lua テーブルの 2 つの主な特徴は、非常に強力で柔軟なデータ構造にもなっています (ただし、最初は完全に理解するのは少し難しいですが)、次のとおりです。
Lua テーブルでは、あらゆる種類の値 ( を除くnil
) をキーとして使用できます。ほとんどの言語では、キーは文字列に制限されているか、事前に宣言する必要がある特定の型に制限されています。Lua では、テーブルはいつでもあらゆる種類の値をキーとして保持できます。したがって、次のように定義されたテーブルは、Lua では完全に合法です。
f = function(x) return x*x end
t = { 1, 2, 3 }
tbl = {
[t] = true, -- `t` is the key (and a table), `true` is the value
[f] = 12, -- `f` is the key (and a function), `12` is the value
[true] = f, -- `true` is the key (boolean), `f` now is used as value
[12] = f, -- `12` is the key (number), `f` again as the value
["yup"] = t, -- `"yup"` is the key (string), `t` now is used as value
}
正の整数キーは、配列をシミュレートするために使用されるため、特別なステータスを持っています。Lua にはarrayという適切な概念がありません。Lua では、配列のようなテーブルを使用します(新しい Lua 5.2 用語を使用すると、シーケンスとも呼ばれます)。多くの場合、Lua のコンテキストで配列という用語が表示されますが、実際にはライターは配列のようなテーブルを意味します。あいまいさが生じないように、簡単にするために以下でも同じことを行います。Luaの配列とは何ですか? 正の整数キーが整数で始まり、整数1
で終わるテーブルですn
、つまり、その正の整数キーは数値 1、2、...、n のみです (これを別の言い方をすると、正の整数キーが集合 {1, 2,...,n} を形成すると言うことです)。その数値はシーケンス (配列) の長さn
と呼ばれ、配列に適用されたときに演算子によって返される数値です。テーブルにこのプロパティがある場合、それはシーケンスと呼ばれます。つまり、配列として参照できます。次の場合、そのプロパティを持つテーブルは依然として配列であることに注意してください。#
- 追加の非数値キー (文字列またはテーブル キーなど) があります。
- 整数以外の追加の数値キーがあります (例:
1.23
)。
- 正ではない追加の整数キー (
0
または など-12
) があります。
「汎用テーブル」と配列の違いは、用語の便宜上のものだけではありません。内部的には、Lua 実装は、テーブルが実際に配列であるかどうかを認識し、配列のようなテーブルを配列として使用するときに Lua テーブルが高いパフォーマンスを発揮できるようにするいくつかの最適化を実行します (たとえば、C で意味されているように)。実際、Lua 標準table
ライブラリは、その関数 ( など) に渡されるテーブルtable.sort
が実際には配列であり、正の整数インデックスを持つエントリでのみ動作すると想定しています。
これらすべてを念頭に置いて、投稿されたコードの難しい点を分析できます。
a = {}
for n in pairs(t) do
a[#a + 1] = n
end
これは一般的な for ループの例です。pairs
(とりわけ)テーブル反復子関数を返します(したがってpairs
、反復子生成関数または反復子ジェネレータipairs
と呼ばれることがあります)。この反復子関数は、機械によって繰り返し呼び出され、 のすべてのキー (および対応する値) を反復処理します。(つまり)には変数が 1 つしか表示されないため、反復中にのキーのみが取得されます。for
t
for
n
t
a[#a + 1] = n
に格納されているキーn
を tableに追加する簡単な方法にすぎません。これは配列であることがわかります。これは、a
1 から始まる連続した正の整数キーのみを持つように反復中に段階的に構築されるためです。#a
の現在の長さa
(最初は0
でa
あり、エントリがないため) は、 のシーケンス プロパティを中断することなくa[#a+1]
、整数キーを使用して新しいエントリを作成します。#a + 1
a
要約すると、そのfor
ループは単にt
配列a
内のすべてのキーを収集して、それらを使用して並べ替えてから出力しtable.sort
ます。
table.sort(a)
for _, n in ipairs(a) do
print (n)
end
前のものは、ジェネリック for の別の例です。この場合、によって返されるイテレータ関数はipairs
、(正の整数) キーとa
反復中の値の両方を (この順序で) 返します。値を出力することのみに関心があるため (は配列であるため、キーは1
、2
、... などになります)、ダミー変数として を使用して (私たちには関係のない) キーを取得します。別の名前を使用することもできましたが、Lua では、このタスクに (完全に合法で通常の) 名前を使用するのが慣用的です。a
_
_
の定義pairsByKeys
は、分析するのが少し難しいです。pairsByKeys
その目的は、反復が特定のキー順序に従って行われることを保証するテーブルを反復できる反復子関数を返す反復子ジェネレータ ( ) を持つことです(Luapairs
は特定の反復順序を保証しません)。次のように使用することを意図しています。
for k, v in pairsByKeys( t ) do
print( k, v )
end
定義を分析してみましょう。すでに分析したコードのロジック (および拡張機能) が 1 つの関数にパックされていることがわかります。
function pairsByKeys(t,f)
local b = {}
for x in pairs(t) do
b[#b + 1] = x
end
table.sort(b, f)
local i = 0
return function()
i = i + 1
return b[i], t[b[i]]
end
end
最初に注意すべきことは、関数 (イテレータ) を返すことです。これは、実際には 3 つの上位値 ( 、および)pairsByKeys
を持つ無名クロージャです。これは、返された関数が機械によって実行されるときに、これらの 3 つの変数を参照できることを意味します(このクロージャはステートフルイテレータの例です)。i
t
b
for
反復子を返す前に、pairsByKeys
反復されるテーブルを「前処理」して、t
上で既に見たようにキーを抽出して並べ替えます。したがってb
、 のすべてのキーが配置t
される順序で保持table.sort(b,f)
されます。この への呼び出しには、を呼び出すときに指定できるコンパレータ関数であるtable.sort
追加の引数があることに注意してください。これにより、さまざまな基準に従ってキーを並べ替えることができます (これが、私が説明した「強化」です)。f
pairsByKeys
変数は、反復されたばかりi
のキーのインデックスを保持します。b
この段階では反復が行われていないため (反復子はまだ作成されていません)。
ここで、イテレータ関数に注目しましょう:
function()
i = i + 1
return b[i], t[b[i]]
end
これが機械によって呼び出されるたびfor
に、 がインクリメントされ、次に反復されるキーであるi
がフェッチされ (順序に関する情報を保持しているため、 から取得されます)、再度使用して対応する値がフェッチされます。この情報を保持する元のテーブルから。キーと値の両方が返されます。これらの 2 つの値は、(各反復で) ループ変数と上記の例に割り当てられた値であり、出力されます。b[i]
b
b
b[i]
t[b[i]]
t
k
v