はじめに
Lua と Luau では # という演算子を使用することでテーブルの長さを取得することができますが、その際の注意点がいくつかあるのでメモしておきます。
この記事での Roblox Studio のバージョンは「Version 0.719.0.7191339 (64bit)」です。
# とは
前述のように Luau では # という演算子を使用することでテーブルの長さを number 型で取得することができます。
例えば nil を含まないただの配列として定義されたテーブルに対して # を使用した以下のコードではログに「2」と表示されます。
local sampleTable = {
"Hello",
"World",
}
print(#sampleTable)
上記のコードのように nil を含まないただの配列として定義されているテーブルに対して # を使用する場合は特に問題ないのですが、配列の中に nil が含まれている場合やそのテーブルが連想配列として定義されている場合、そのテーブルの中に配列と連想配列が混在している場合などには少し注意が必要です。
この # という演算子の挙動については公式ドキュメントで以下のように説明されています。
The length of a table
2.5.5 – The Length Operator | Lua 5.1 Reference Manualtis defined to be any integer index n such that t[n] is not nil and t[n+1] is nil; moreover, if t[1] is nil, n can be zero.
つまり、sampleTable という名前のテーブルに対して # を使用すると sampleTable[1] から順に取得した Value の中で sampleTable[n] が nil ではなく、その次の Value(sampleTable[n + 1])が nil になるような要素の n が返されます。
(この n は整数です。)
そして、そのテーブルの Key が number 型以外だった場合や、Key が number 型だったとしても「1」という Key が無かった場合などは0という値が返ってきます。
テーブルの長さを正しく取得できない具体例
配列の要素に nil が含まれている場合
Key が number 型かつ、それが1から連続した値になっていたとしてもその配列の中に nil が含まれているとテーブルの長さを正しく取得できないことがあります。
例えば以下のコードでは string 型の配列としてテーブルを定義していますが、nil ではない「Hello」と「World」という要素の間に nil という要素が存在しています。
local sampleTable = {
"Hello",
nil,
"World",
}
print(#sampleTable)
これは内部的には Key が number 型で Value が string 型の連想配列になっており、Key が1である要素の Value は「Hello」という nil ではない値のため、sampleTable[1] ~= nil かつ sampleTable[1 + 1] == nil で「#sampleTable」の結果は「1」になりそうですが、実際は「3」になります。
この配列の中に「穴」があるときの # の挙動については公式ドキュメントで以下のように説明されています。
If the array has “holes” (that is, nil values between other non-nil values), then #t can be any of the indices that directly precedes a nil value (that is, it may consider any such nil value as the end of the array).
2.5.5 – The Length Operator | Lua 5.1 Reference Manual
上記の Lua の公式ドキュメントを読む限りでは1と3がランダムに返ってきそうな気がしますが、実際は3のみが返ってきました。
Luau のソースを追えていないのですが、Luau では最後の配列部分の結果を採用しやすいロジックになっているのかもしれません…
Key が number 型ではない場合
他にもそのテーブルの Key が number 型ではない場合はそのテーブルの長さを正しく取得できません。
例えば以下のコードでは Key と Value がともに string 型の連想配列を定義し、その長さをログに表示しようとしています。
local sampleTable = {
hello = "Hello",
world = "World",
}
print(#sampleTable)
この場合はログに「0」と表示されます。
これは sampleTable[1] が nil になるためです。
ちなみに先ほどのコードのように配列部分を持たないテーブルに対して # を使用したコードを記述すると「Using ‘#’ on a table without an array part is likely a bug」というエラーが発生してくれます。
Key が number 型の場合
そのテーブルの Key が number 型だったとしてもそのテーブルの長さを正しく取得できないことがあります。
例えば以下のコードのように number 型の Key を明示して定義したテーブルに対して # を使用してみます。
Key は1、2 と続いています。
local sampleTable = {
[1] = "Hello",
[2] = "World",
}
print(#sampleTable)
この場合はログに「2」と表示され、テーブルの長さを意図した通りに取得できていることがわかります。
次は以下のコードのように Key を1以外の値から始めてみます。
local sampleTable = {
[2] = "Hello",
[3] = "World",
}
print(#sampleTable)
するとログには「0」と表示されます。
これは Key が number 型ではなかったときのように sampleTable[1] が nil になってしまうためです。
では Key が1である要素があれば問題ないのかというと、そういうわけでもありません。
例えば以下のコードでは Key が number 型であり、Key が1である要素もありますが、その次の要素の Key が2ではなく、3になっています。
local sampleTable = {
[1] = "Hello",
[3] = "World",
}
print(#sampleTable)
このコードの処理を実行するとログには「1」と表示されます。
これは、sampleTable[1] は nil ではありませんが、sampleTable[1 + 1] が nil になってしまうためです。
配列と連想配列が混在している場合
最後に配列と連想配列が混在しているテーブルに対して # を使用したときの挙動を確認してみます。
例えば以下のコードでは、Key が string 型である要素と Key が number 型である要素を2つずつ含んだテーブルを定義しています。
local sampleTable = {
hello = "Hello",
world = "World",
"Hello",
"World",
}
print(#sampleTable)
この場合はログに「2」と表示され、テーブルの中の配列部分の要素だけをカウントしているような挙動になっているのがわかります。
以下のように要素の順番を入れ替えた場合も同様です。
local sampleTable = {
hello = "Hello",
"Hello",
world = "World",
"World",
}
最後に
まとめ
このように # を使用してテーブルの長さを取得する際は意図しない値が返ってくる場合があるので、基本的には nil を含まないことが保証されている配列に対してのみ # を使用すると良いかと思います。
