Luaにおける表取長さ深さ解析
5843 ワード
作者:バカカタツムリリンク:https://www.jianshu.com/p/1e8ab8fe55e4出典:簡書
長さをとる関数
配列の長さは
≪インスタンス|Instance|emdw≫
例1(全て配列部)
解釈:
例2
解釈:
例3
解釈:
例4(全てhash部)
説明:
例5
解釈:tは配列要素がなく、
例6(配列部分もhash部分も含む)
解釈:配列部分の長さは3,hash部分の長さは1である.
例7
解釈:配列部分の長さは3,hash部分の長さは1である.
例8
解釈:配列部分の長さは4,hash部分の長さは1である.
以上はtableの作成時に配列部分とhash部分が決定されたが、キー値が追加されると
例9
1つ目は1で、上記の例を参考にして、キー値を追加したときに再割り当てを行い、結果として配列部分の長さは4、hash部分は0であり、
Luaのtableを参照して整数キー値を追加します.配列部
例10
キー値を追加すると、再割り当てされ、結果として配列部分の長さが1、hash部分が2となり、
例11
まとめ(1)配列やハッシュバケツ部分を1つのテーブルに混用しないようにし,1つのテーブルに1種類のデータしか格納しないことが望ましい.luaの実現には確かに両者の統一表現の遍歴が提供されているが,これは利用者がこの2つの方式を混用すべきであることを意味するものではない.(2)nil値をできるだけテーブルに格納しないようにすると,長さ取り操作の挙動が不安定になる.(3)この操作のコストが大きいため,このlua解釈器の背後にある動作を予め割り当て,配列部分のみを用いるなどの戦略により回避することで,多くの効率を向上させることができる.
長さをとる関数
/*
** Try to find a boundary in table 't'. A 'boundary' is an integer index
** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil).
*/
lua_Unsigned luaH_getn (Table *t) {
unsigned int j = t->sizearray;
if (j > 0 && ttisnil(&t->array[j - 1])) { // ,
/* there is a boundary in the array part: (binary) search for it */
unsigned int i = 0;
while (j - i > 1) {
unsigned int m = (i+j)/2;
if (ttisnil(&t->array[m - 1])) j = m;
else i = m;
}
return i;
}
/* else must find a boundary in hash part */
else if (isdummy(t)) /* hash part is empty? */ // , hash
return j; /* that is easy... */
else return unbound_search(t, j); // ,hash
}
/*
,
, hash
,hash
unbound_search(t, j),
*/
static lua_Unsigned unbound_search (Table *t, lua_Unsigned j) {
lua_Unsigned i = j; /* i is zero or a present index */
j++;
/* find 'i' and 'j' such that i is present and j is not */
while (!ttisnil(luaH_getint(t, j))) { // t[j] , t[2*j] , j 2*j
i = j;
if (j > l_castS2U(LUA_MAXINTEGER) / 2) { /* overflow? */
/* table was built with bad purposes: resort to linear search */
i = 1;
while (!ttisnil(luaH_getint(t, i))) i++;
return i - 1;
}
j *= 2;
}
/* now do a binary search between them */
while (j - i > 1) {
lua_Unsigned m = (i+j)/2; //
if (ttisnil(luaH_getint(t, m))) j = m;
else i = m;
}
return i;
}
配列の長さは
j
であり、hash部分はj+1
から遍歴し、j
は毎回2倍に拡大し、t[j]
は空ではなく、t[2*j]
は空であり、その後、二分法によって検索し、最終的な値を見つけた.≪インスタンス|Instance|emdw≫
例1(全て配列部)
local t = {1, 2, 3, 4}
print(#t) -- 4
解釈:
t
はhash部分がなく、配列長は4である.t[4]
は空ではないので、j = 4
に戻ります.例2
local t = {1, nil, 3}
print(#t) -- 3
解釈:
t
はhash部分がなく、配列長は3である.t[3]
は空ではないので、j = 3
に戻ります.例3
local t = {1, nil, 3, nil}
print(#t) -- 1
解釈:
t
はhash部分がなく、配列長は4である.t[4]
が空なので、二分法で検索 i = 0, j = 4, m = (i+j)/2 = 2, array[2] , j = m = 2;
i = 0, j = 2, m = (i+j)/2 = 1, array[1] , i = m = 1;
i = 1, j = 2, j - i > 1 , , i = 1。
例4(全てhash部)
local t = {[1] = 1, [3] = 3, [5] = 5, [6] = 6, [7] = 7}
print(#t) -- 1
説明:
t
には配列要素がありません.unbound_search
関数が呼び出され、hash部分はj = 1
から巡回し、i
は前のj
の値を記録する. i = 0, j = 1, t[1] , i = j = 1, j = 2
i = 1, j = 2, t[1] , j - i = 1 > 1 , i = 1。
例5
local t = {[1] = 1, [2] = 2, [4] = 4, [6] = 6}
print(#t) -- 6
解釈:tは配列要素がなく、
unbound_search
関数を呼び出し、hash部分はj = 1
から遍歴する. i = 0, j = 1, t[1] , i = j = 1, j = 2
i = 1, j = 2, t[2] , i = j = 2, j = 4
i = 2, j = 4, t[4] , i = j = 4, j = 8
i = 4, j = 8, t[8] , j - i = 4 > 1 ,
i = 4, j = 8, m = (i+j)/2 = 6, t[6] nil, i = m = 6;
i = 6, j = 8, m = (i+j)/2 = 7, t[7] nil, j = m = 7;
i = 6, j = 7, j - i > 1 , , i = 6。
例6(配列部分もhash部分も含む)
local t = {1, 2, 3, [5] = 5}
print(#t) -- 3
解釈:配列部分の長さは3,hash部分の長さは1である.
t[3]
は空ではなく、hash部分は空ではないため、unbound_search
関数が呼び出され、hash部分はj = 4
から遍歴する i = 3, j = 4, t[4] , j - i = 1 > 1 , i = 3
例7
local t = {1, 2, 3, [4] = 4}
print(#t) -- 4
解釈:配列部分の長さは3,hash部分の長さは1である.
t[3]
は空ではなく、hash部分は空ではないため、unbound_search
関数が呼び出され、hash部分はj = 4
から遍歴する i = 3, j = 4, t[4] , i = j = 4, j = 8
i = 4, j = 8, t[8] , j - i = 4 > 1
, i = 4
例8
local t = {1, 2, 3, nil, [5] = 5}
print(#t) -- 3
解釈:配列部分の長さは4,hash部分の長さは1である.
t[4]
が空であるため、配列部分では二分法で検索し、例3を参照し、最終的にはi = 3
を返す.以上はtableの作成時に配列部分とhash部分が決定されたが、キー値が追加されると
rehash
関数が呼び出され、配列とhash部分が再割り当てされる可能性がある.例9
local t = {1, [4] = 4}
print(#t) -- 1
t[3] = 3 --
print(#t) -- 4
1つ目は1で、上記の例を参考にして、キー値を追加したときに再割り当てを行い、結果として配列部分の長さは4、hash部分は0であり、
local t = {1, nil, 3, 4}
Luaのtableを参照して整数キー値を追加します.配列部
t[4]
は空ではないので、i = 4
を返します.例10
local t = {1, [5] = 5}
t[3] = 3
print(#t) -- 1
キー値を追加すると、再割り当てされ、結果として配列部分の長さが1、hash部分が2となり、
local t = {1, [3] = 3, [5] = 5}
例11
local t = {1, 2, [5] = 5}
t[4] = 4
print(#t) -- 5
local t = {1, 2, nil, 4, [5] = 5}
まとめ(1)配列やハッシュバケツ部分を1つのテーブルに混用しないようにし,1つのテーブルに1種類のデータしか格納しないことが望ましい.luaの実現には確かに両者の統一表現の遍歴が提供されているが,これは利用者がこの2つの方式を混用すべきであることを意味するものではない.(2)nil値をできるだけテーブルに格納しないようにすると,長さ取り操作の挙動が不安定になる.(3)この操作のコストが大きいため,このlua解釈器の背後にある動作を予め割り当て,配列部分のみを用いるなどの戦略により回避することで,多くの効率を向上させることができる.