已排序關聯表格 |
|
--// CHILL CODE � //-- -- table.ordered( [comp] ) -- -- Lua 5.x add-on for the table library. -- Table using sorted index. Uses binary table for fast Lookup. -- https://lua-users.dev.org.tw/wiki/OrderedTable by PhilippeFremy -- table.ordered( [comp] ) -- Returns an ordered table. Can only take strings as index. -- fcomp is a comparison function behaves behaves just like -- fcomp in table.sort( t [, fcomp] ). function table.ordered(fcomp) local newmetatable = {} -- sort func newmetatable.fcomp = fcomp -- sorted subtable newmetatable.sorted = {} -- behavior on new index function newmetatable.__newindex(t, key, value) if type(key) == "string" then local fcomp = getmetatable(t).fcomp local tsorted = getmetatable(t).sorted table.binsert(tsorted, key , fcomp) rawset(t, key, value) end end -- behaviour on indexing function newmetatable.__index(t, key) if key == "n" then return table.getn( getmetatable(t).sorted ) end local realkey = getmetatable(t).sorted[key] if realkey then return realkey, rawget(t, realkey) end end local newtable = {} -- set metatable return setmetatable(newtable, newmetatable) end --// table.binsert( table, value [, comp] ) -- -- LUA 5.x add-on for the table library. -- Does binary insertion of a given value into the table -- sorted by [,fcomp]. fcomp is a comparison function -- that behaves like fcomp in in table.sort(table [, fcomp]). -- This method is faster than doing a regular -- table.insert(table, value) followed by a table.sort(table [, comp]). function table.binsert(t, value, fcomp) -- Initialise Compare function local fcomp = fcomp or function( a, b ) return a < b end -- Initialise Numbers local iStart, iEnd, iMid, iState = 1, table.getn( t ), 1, 0 -- Get Insertposition while iStart <= iEnd do -- calculate middle iMid = math.floor( ( iStart + iEnd )/2 ) -- compare if fcomp( value , t[iMid] ) then iEnd = iMid - 1 iState = 0 else iStart = iMid + 1 iState = 1 end end local pos = iMid+iState table.insert( t, pos, value ) return pos end -- Iterate in ordered form -- returns 3 values i, index, value -- ( i = numerical index, index = tableindex, value = t[index] ) function orderedPairs(t) return orderedNext, t end function orderedNext(t, i) i = i or 0 i = i + 1 local index = getmetatable(t).sorted[i] if index then return i, index, t[index] end end
測試
t2= table.ordered() t2["A"] = 1 t2.B = 2 t2.C = 3 t2.D = 4 t2.E = 5 t2.F = 6 t2.G = 7 t2.H = 8 print("Normal iteration ordered table") -- will iterate over the table by next index table.foreach( t2, print )
結果
Normal iteration ordered table A 1 C 3 B 2 E 5 D 4 G 7 F 6 H 8
測試
print("Ordered Iteration") for i,index,v in orderedPairs(t2) do print(index, v) end
結果
Ordered Iteration A 1 B 2 C 3 D 4 E 5 F 6 G 7 H 8
測試
-- same example but reveres ordered t2= table.ordered( function(a,b) return b < a end ) t2["A"] = 1 t2.B = 2 t2.C = 3 t2.D = 4 t2.E = 5 t2.F = 6 t2.G = 7 t2.H = 8
print("Ordered Iteration of Reverse") for i,index,v in orderedPairs(t2) do print(index, v) end
結果
Ordered Iteration of Reverse H 8 G 7 F 6 E 5 D 4 C 3 B 2 A 1
由於無法刪除任何條目所造成的這個問題,另一選項是完全轉換為二進制表格,並使用 t[index]
來存取它,同時在 metatable 中執行排序的工作。
--// CHILL CODE � //-- -- table.ordered( [sorted reverse], [type] ) v 2 -- Lua 5.x add-on for the table library -- Table using sorted index, with binary table for fast lookup. -- https://lua-users.dev.org.tw/wiki/OrderedTable by PhilippeFremy -- table.ordered( [sorted reverse], [type] ) -- Gives you back a ordered table, can only take entered type -- as index returned by type(index), by default "string" -- sorted reverse, sorts the table in reverse order, else normal -- stype is the deault index type returned by type( index ), -- by default "string", it is only pssible to set one type as index -- will effectively create a binary table, and will always lookup -- through binary when an index is called function table.ordered(ireverse, stype) local newmetatable = {} -- set sort function if ireverse then newmetatable._ireverse = 1 function newmetatable.fcomp(a, b) return b[1] < a[1] end else function newmetatable.fcomp(a, b) return a[1] < b[1] end end -- set type by default "string" newmetatable.stype = stype or "string" -- fcomparevariable function newmetatable.fcompvar(value) return value[1] end -- sorted subtable newmetatable._tsorted = {} -- behaviour on new index function newmetatable.__newindex(t, key, value) if type(key) == getmetatable(t).stype then local fcomp = getmetatable(t).fcomp local fcompvar = getmetatable(t).fcompvar local tsorted = getmetatable(t)._tsorted local ireverse = getmetatable(t)._ireverse -- value is given so either update or insert newly if value then local pos, _ = table.bfind(tsorted, key, fcompvar, ireverse) -- if pos then update the index if pos then tsorted[pos] = {key, value} -- else insert new value else table.binsert(tsorted, {key, value}, fcomp) end -- value is nil so remove key else local pos, _ = table.bfind(tsorted, key, fcompvar, ireverse) if pos then table.remove(tsorted, pos) end end end end -- behavior on index function newmetatable.__index(t, key) if type(key) == getmetatable(t).stype then local fcomp = getmetatable(t).fcomp local fcompvar = getmetatable(t).fcompvar local tsorted = getmetatable(t)._tsorted local ireverse = getmetatable(t)._ireverse -- value if key exists local pos, value = table.bfind(tsorted, key, fcompvar, ireverse) if pos then return value[2] end end end -- set metatable return setmetatable({}, newmetatable) end --// table.binsert( table, value [, comp] ) -- Lua 5.x add-on for the table library -- Binary inserts given value into the table sorted by [,fcomp] -- fcomp is a comparison function that behaves just like -- fcomp in table.sort( table [, comp] ). -- This method is faster than doing a regular -- table.insert(table, value) followed by a table.sort(table [, comp]). function table.binsert(t, value, fcomp) -- Initialize compare function local fcomp = fcomp or function(a, b) return a < b end -- Initialize numbers local iStart, iEnd, iMid, iState = 1, table.getn( t ), 1, 0 -- Get insert position while iStart <= iEnd do -- calculate middle iMid = math.floor((iStart + iEnd) / 2) -- compare if fcomp(value , t[iMid]) then iEnd = iMid - 1 iState = 0 else iStart = iMid + 1 iState = 1 end end local pos = iMid+iState table.insert(t, pos, value) return pos end --// table.bfind(table, value [, compvalue] [, reverse]) -- Lua 5.x add-on for the table library. -- Binary searches the table for value. -- If the value is found it returns the index and the value of -- the table where it was found. -- fcompval, if given, is a function that takes one value and -- returns a second value2 to be compared with the input value, -- e.g. compvalue = function(value) return value[1] end -- If reverse is given then the search assumes that the table -- is sorted with the biggest value on position 1. function table.bfind(t, value, fcompval, reverse) -- Initialize functions fcompval = fcompval or function(value) return value end fcomp = function(a, b) return a < b end if reverse then fcomp = function(a, b) return a > b end end -- Initialize Numbers local iStart, iEnd, iMid = 1, table.getn(t), 1 -- Binary Search while (iStart <= iEnd) do -- calculate middle iMid = math.floor((iStart + iEnd) / 2) -- get compare value local value2 = fcompval(t[iMid]) if value == value2 then return iMid, t[iMid] end if fcomp(value , value2) then iEnd = iMid - 1 else iStart = iMid + 1 end end end -- Iterate in ordered form -- returns 3 values i , index, value -- ( i = numerical index, index = tableindex, value = t[index] ) function orderedPairs(t) return orderedNext, t end function orderedNext(t, i) i = i or 0 i = i + 1 local indexvalue = getmetatable(t)._tsorted[i] if indexvalue then return i, indexvalue[1], indexvalue[2] end end
測試
t2 = table.ordered() if t2 then print( t2 ) end t2["A"] = 1 t2.B = 2 t2.C = 3 t2.D = 4 t2.E = 5 t2.F = 6 t2.G = 7 t2.H = 8 print("Ordered Iteration") for i,index,v in orderedPairs(t2) do print( index, v ) end
結果
table: 07864640 Ordered Iteration A 1 B 2 C 3 D 4 E 5 F 6 G 7 H 8
測試
-- same example but reveres ordered t2= table.ordered( "reverse" ) t2["A"] = 1 t2.B = 2 t2.C = 3 t2.D = 4 t2.E = 5 t2.F = 6 t2.G = 7 t2.H = 8 print("Ordered Iteration of Reverse") for i,index,v in orderedPairs(t2) do print(index, v) end
結果
Ordered Iteration of Reverse H 8 G 7 F 6 E 5 D 4 C 3 B 2 A 1
測試
print("Set one Letter nil") t2.E = nil print("Ordered Iteration of Reverse") for i,index,v in orderedPairs(t2) do print(index, v) end
結果
Set one Letter nil Ordered Iteration of Reverse H 8 G 7 F 6 D 4 C 3 B 2 A 1
測試
print("Update one value") t2.F = "updated" print("Ordered Iteration of Reverse") for i,index,v in orderedPairs(t2) do print(index, v) end
結果
Update one value Ordered Iteration of Reverse H 8 G 7 F updated D 4 C 3 B 2 A 1
測試
print("Add with a no confirm key") -- will simply be not added t2[6] = "add" print( "Add a key" ) t2.a = "new key1" t2.Z = "new key 2" t2.d = "??" print( "Ordered Iteration of Reverse" ) for i,index,v in orderedPairs( t2 ) do print( index, v ) end -- get a key print( t2.Z )
結果
Add with a no confirm key Add a key Ordered Iteration of Reverse d ?? a new key1 Z new key 2 H 8 G 7 F updated D 4 C 3 B 2 A 1 new key 2
測試
-- Speed testing -- build a n big inassociative table -- search it n2 times by hash clac used tim n1 = 100000 n2 = 100000 t = {} i0 = os.clock() for i = 1, n1 do t[tostring(i)] = i end local i1 = os.clock() for i = 1, n2 do local v = i, t[tostring(i)] --print(v , i) end print("Normal test of inassociative table") print("Time to add "..n2.." Items: "..(i1-i0)) print(os.clock()) print(i1) print(os.clock() - i1) print("Time to search "..n1.." Items: "..(os.clock() - i1)) -- do one sort i0 = os.clock() local ts = {} table.foreach(t, function(i,v) table.insert(ts, {i,i}) end) table.sort(ts, function(a, b) return b[1] < a[1] end ) print("Normalsort time: "..(os.clock()-i0)) -- Speed test with a ordered table t = table.ordered() i0 = os.clock() for i = 1, n1 do t[tostring(i)] = i end local i1 = os.clock() for i = 1, n2 do local v = i, t[tostring(i)] --print(v , i) end print("Normal test of Ordered inassociative table") print("Time to add "..n2.." Items: "..(i1-i0)) print(os.clock()) print(i1) print("Time to search "..n1.." Items: "..(os.clock() - i1))
結果
Normal test of inassociative table Time to add 100000 Items: 1.6409999999996 19960.765 19960.125 0.63999999999942 Time to search 100000 Items: 0.63999999999942 Normalsort time: 2.8280000000013 Normal test of Ordered inassociative table Time to add 100000 Items: 52.281999999999 20021.593 20015.875 Time to search 100000 Items: 5.7180000000008
正如所示,與正常的索引新增相比,當新增新的鍵值時程式碼會變得非常緩慢,大約是 100 倍,而當我們在搜尋索引時速度大約慢了 10 倍,我想這是可以接受的。接著我從正常的表格中建立一個排序的索引陣列,結果又比在二進制表格上的搜尋次數要快。這讓我回到了 SortedIteration,它是執行的最佳方式,除非你需要的執行次數超過新增或檢查值,在這種特殊情況下才會比較快好嗎,至少知道這點也不錯 :)