lua中,對table.remove()的改進


^_^內容原創,禁止轉載


前幾天在處理項目某個bug的時候發現代碼中使用了ipairs()+table.remove()刪除元素,因為歷史配置原因,導致這段代碼一直沒出現錯誤。lua中,在for循環調用函數ipairs時,ipairs會返回3個值供for保存,迭代函數、不可變狀態表、初始控制變量0,for的每次調用,都會把狀態表和控制變量傳入迭代函數,調用迭代函數,把控制變量+1,再獲取狀態表中相應元素,並把兩者返回,直至遇到nil結束,控制變量不會也不可能因為表的改變而實時刷新;而函數table.remove除了刪除、返回指定序列上的元素,還會把后面的元素往前移動。故而知,當表中相鄰的兩個元素都需要刪除的時候,就會發生刪除不干凈的情況。那像這種情況需要遍歷表刪除的,該以怎樣一種方式去迭代,以達到正確刪除元素的目的。首先,如果是無序表,直接把對應位置上的元素置nil;如果是有序表,比較常用的方法有以下3種:

1 local a = {1, 1, 2, 3, 4, 4} 2 for i = #a, 1, -1 do
3     if a % 2 == 0 then
4         table.remove(a, i) 5     end
6 end
1.數值型for從后往前遍歷
 1 local a = {1, 1, 2, 3, 4, 4}  2 local index = 1
 3 local v = a[index]  4 while v do
 5     if v % 2 == 0 then
 6         table.remove(a, index)  7     else
 8         index = index + 1
 9     end
10 
11     v = a[index] 12 end
2.while控制下標方式
1 local a = {1, 1, 2, 3, 4, 4} 2 local tmp = {} 3 for k, v in ipairs(a) do
4     if v % 2 == 0 then
5         table.insert(tmp, v) 6     end
7 end
8 a = tmp
3.構建臨時表方式

這3種方式都可以實現對表元素的刪除,但細想下或許還有更好的方式去實現。首先,回到table.remove()本身,上面說到,table.remove()刪除位置上的元素后還會把此位置后面的元素往前移,這里涉及到了一個效率問題。如果是有序表,在某個時刻僅需要刪除一個位置上的元素且繼續保持有序,table.remove()是必然選擇,但是,如果需要遍歷表刪除,那么選擇table.remove()是否會顯得有點不夠明智?明顯地,每一次remove,都移動了后面的元素,而loop+table.remove(),就相當於一步一個腳印的把元素移動到合適的位置,這是一個明顯的效率問題。基於此,上面的方式3是否成了最佳選擇,其實表的構造也需要時間,雖然基本可以忽略不計,但申請額外的空間來完成刪除無論怎樣看起來都覺得不太優雅,而且像這種情況不能在新表構造的時候明確具體長度,如果因為原表較大,觸發了新表的luaH_resize,即內存重新分配,這也不是我們想看到的情況。那應該以怎樣一種方式更好地實現上面loop+table.remove()?

  在lua中回收一個元素,直接把它置nil即可。於是,對於有序表,是否可以以下面的方式實行替代:

 1 local a = {1, 1, 2, 3, 4, 4}  2 local index, r_index, length = 1, 1, #a  3 while index <= length do
 4     local v = a[index]  5     a[index] = nil
 6     if not (v % 2 == 0) then
 7         a[r_index] = v  8         r_index = r_index + 1
 9     end
10 
11     index = index + 1
12 end
Achieve1

稍微封裝一下:

 1 local tb = {}  2 
 3 function tb.remove(obj, rm_func)  4     if type(obj) ~= "table" or type(rm_func) ~= "function" then
 5         return
 6     end
 7 
 8     local index, r_index, length = 1, 1, #obj  9     while index <= length do
10         local v = obj[index] 11         obj[index] = nil
12         if not rm_func(v) then
13             obj[r_index] = v 14             r_index = r_index + 1
15         end
16 
17         index = index + 1
18     end
19 end
20 
21 return tb
Achieve2

Achieve2彷佛是一種不錯的替代loop+table.remove()的方案,既不用在刪除元素的時候移動后面的元素,也不需要額外的元素儲存空間,時間復雜度妥妥的O(n),空間復雜度O(1),下面附上測試代碼來看看這幾種方式的運行時間對比:

 1 local tb = {}  2 
 3 function tb.remove(obj, rm_func)  4     if type(obj) ~= "table" or type(rm_func) ~= "function" then
 5         return
 6     end
 7 
 8     local index, r_index, length = 1, 1, #obj  9     while index <= length do
10         local v = obj[index] 11         obj[index] = nil
12         if not rm_func(v) then
13             obj[r_index] = v 14             r_index = r_index + 1
15         end
16 
17         index = index + 1
18     end
19 end
20 
21 local a = {} 22 local b = {} 23 local c = {} 24 local d = {} 25 local length = 1024*128
26 for i = 1, length do
27     a[i] = i 28     b[i] = i 29     c[i] = i 30     d[i] = i 31 end
32 
33 rm_func = function(value) 34     return value % 2 == 0
35 end
36 local start_time = os.clock() 37 
38 --test-(1.數值型for從后往前遍歷)
39 for i = #a, 1, -1 do
40     if rm_func(a[i]) then
41         table.remove(a, i) 42     end
43 end
44 print("1.數值型for從后往前遍歷 time:", os.clock() - start_time) 45 
46 --test-(2.while控制下標方式)
47 start_time = os.clock() 48 local index = 1
49 local v = b[index] 50 while v do
51     if rm_func(v) then
52         table.remove(b, index) 53     else
54         index = index + 1
55     end
56     
57     v = b[index] 58 end
59 print("2.while控制下標方式 time:", os.clock() - start_time) 60 
61 --test-(3.構建臨時表方式)
62 start_time = os.clock() 63 local tmp = {} 64 for k, v in ipairs(c) do
65     if v % 2 == 0 then
66         table.insert(tmp, v) 67     end
68 end
69 c = tmp 70 print("3.構建臨時表方式 time:", os.clock() - start_time) 71 
72 --test-(tb.remove)
73 start_time = os.clock() 74 tb.remove(d, rm_func) 75 print("tb.remove time:", os.clock() - start_time)
Test Code

即使事先知道Achieve2具有更高效率,但看到對比輸出,依然為Achieve2感到驚訝,即使是通過構建臨時表的方式3依然比Achieve2差了9倍左右,而且毫無疑問方式3內存占用更高;當然,實際測試時間還受到具體環境影響,但是Achieve2比loop+table.remove()效率更高已經可以蓋棺定論(對於Achieve2的測試本人進行了不下百次,Achieve2的實現絕對是健康的實現)。


  到這里已經明確Achieve2是更好的替代方案,但是否可以讓Achieve2變得更健全一點?如果表是無序表,或者表既有無序部分也有有序部分,當中的元素都需要遍歷判斷刪除怎么辦,上述的Achieve2只能用於有序表,就是表的元素只存在於table的數組部分,哈希部分為空的情況下,那么是否能進一步更改Achieve2的實現使適用於所有情況?首先,要明確的是,lua提供的運算符和表標准庫函數都只能獲取表數組部分長度,對於總體長度,是無法直接獲取的,所以不在事先遍歷一次表的情況下無法判斷一個表到底是有序表還是無序表;其次,要使Achieve2適用所有情況,必須使用pairs()遍歷,使用pairs()遍歷,具體算法是否依然能把Achieve2的時間復雜度控制在O(n)?

  詳細了解一下函數pairs機制,函數pairs在調用的時候會返回lua的一個基本函數next和不可變狀態表t,調用next(t, key)時,該函數會以隨機次序返回表中的下一個key及其對應的值;調用next(t, nil)時,返回表中的第一個鍵值對,所有元素遍歷完時,函數next返回nil,for循環結束,for循環總是會把表達式列表的結果調整為3個值,所以,在首次調用pairs()的時候,得到的相當於next, t, nil;next函數的底層lua實現luaH_next中,總是先遍歷數組部分,再遍歷哈希部分,基於pairs的這些具體行為,我們可以得到Achieve3:

 1 local tb = {}  2 
 3 function tb.remove(obj, rm_func)  4     if type(obj) ~= "table" or type(rm_func) ~= "function" then
 5         return
 6     end
 7 
 8     local r_index, length = 1, #obj  9     for k, v in pairs(obj) do        --lua5.3可以通過math.type(k)判斷
10         if type(k) == "number" and math.floor(k) == k and k > 0 and k <= length then
11             local tmp = v 12             obj[k] = nil
13             if not rm_func(tmp) then
14                 obj[r_index] = tmp 15                 r_index = r_index + 1
16             end
17         else
18             if rm_func(v) then
19                 obj[k] = nil
20             end
21         end
22     end
23 end
24 
25 return tb
Achieve3

Achieve3好像是個不錯的實現,但是看着那一堆判斷就頭疼,那還有沒有更好的實現方法,有,就是先手動遍歷表數組部分,然后自己實現pairs行為,使只遍歷哈希部分,下面是Achieve4:

 1 local tb = {}  2 
 3 function tb.remove(obj, rm_func)  4     if type(obj) ~= "table" or type(rm_func) ~= "function" then
 5         return
 6     end
 7 
 8     local index, r_index, length = 1, 1, #obj  9     while index <= length do
10         local v = obj[index] 11         obj[index] = nil
12         if not rm_func(v) then
13             obj[r_index] = v 14             r_index = r_index + 1
15         end
16 
17         index = index + 1
18     end
19 
20     local function _pairs(tb) 21         if length == 0 then
22             return next, tb, nil
23         else
24             return next, tb, length 25         end
26     end
27 
28     for k, v in _pairs(obj) do
29         if rm_func(v) then
30             obj[k] = nil
31         end
32     end
33 end
34 
35 return tb
Achieve4

但是我們可能會面臨着一種具體情況,那就是我們希望在對表的有序部分刪除元素后,后面的元素保持不動,於是再度改寫Achieve4,得到Achieve5(如果項目中有封裝table的操作文件,可以把_pair單獨拿出來,這樣就不用每次運行都創建一個閉包):

 1 local tb = {}  2 
 3 function tb.remove(obj, rm_func, to_sequence)  4     if type(obj) ~= "table" or type(rm_func) ~= "function" then
 5         return
 6     end
 7 
 8     local length = 0
 9     if to_sequence then
10         length = #obj 11         local index, r_index = 1, 1
12         while index <= length do
13             local v = obj[index] 14             obj[index] = nil
15             if not rm_func(v) then
16                 obj[r_index] = v 17                 r_index = r_index + 1
18             end
19 
20             index = index + 1
21         end
22     end
23 
24     local function _pairs(tb) 25         if length == 0 then
26             return next, tb, nil
27         else
28             return next, tb, length 29         end
30     end
31 
32     for k, v in _pairs(obj) do
33         if rm_func(v) then
34             obj[k] = nil
35         end
36     end
37 end
38 
39 return tb
Achieve5

  好了,到此文章已經差不多結束,上述內容都是本人在工作中的實際見解,純屬原創。這也是本人的第一次寫博,水平有限,以后還會繼續把自己的經驗分享出來,如果有錯誤和可以改進的地方還請不吝指出。下面附上本人寫的一些拓展table函數(主要用於個人測試):

 1 local tb = {}  2 
 3 --to_sequence可選且只會在表有序部分起作用,時間復雜度O(n)
 4 function tb.remove(obj, rm_func, to_sequence)  5     if type(obj) ~= "table" or type(rm_func) ~= "function" then
 6         return
 7     end
 8 
 9     local length = 0
 10     if to_sequence then
 11         length = #obj  12         local index, r_index = 1, 1
 13         while index <= length do
 14             local v = obj[index]  15             obj[index] = nil
 16             if not rm_func(v) then
 17                 obj[r_index] = v  18                 r_index = r_index + 1
 19             end
 20 
 21             index = index + 1
 22         end
 23     end
 24 
 25     local function _pairs(tb) --lua_next實現是迭代完數組部分再迭代哈希部分
 26         if length == 0 then
 27             return next, tb, nil
 28         else
 29             return next, tb, length  30         end
 31     end
 32 
 33     for k, v in _pairs(obj) do
 34         if rm_func(v) then
 35             obj[k] = nil
 36         end
 37     end
 38 end
 39 
 40 function tb.duplicate(obj)  41     local tb_note = {} --使指向行為與原表一致
 42     local copy_func  43     copy_func = function(obj)  44         if type(obj) ~= "table" then
 45             return obj  46         elseif tb_note[obj] then
 47             return tb_note[obj]  48         end
 49 
 50         local dup = {}  51         tb_note[obj] = dup  52         for k, v in pairs(obj) do
 53             dup[copy_func(k)] = copy_func(v)  54         end
 55         setmetatable(dup, getmetatable(obj))  56         return dup  57     end
 58 
 59     return copy_func(obj)  60 end
 61 
 62 function tb.tostring(obj)  63     local tb_note = {} --防止相同表轉換多次
 64     local function serialize(value)  65         if tb_note[value] then
 66             return tb_note[value]  67         elseif type(value) == "number" then
 68             return string.format("%d", value) --%a
 69         elseif type(value) == "string" then
 70             return string.format("%q", value) --5.3.3版本后可以使用%q轉化number,nil,boolean
 71         elseif type(value) == "table" then
 72             local str = "{"
 73             local index_tb = tb.getsortedindexlist(value)  74             for _, v in ipairs(index_tb) do
 75                 str = str.."["..serialize(v).."]="..serialize(value[v])..","
 76             end
 77             local mt = getmetatable(value)  78             if mt then str = str.."[\"metatable\"]="..serialize(mt).."," end
 79             str = str.."}"
 80             
 81             tb_note[value] = str  82             return str  83         else
 84             return tostring(value)  85         end
 86     end
 87 
 88     return serialize(obj)  89 end
 90 
 91 local type_value = {["number"] = 1, ["string"] = 2, ["userdata"] = 3, ["function"] = 4, ["table"] = 5}  92 function tb.getsortedindexlist(obj)  93     local index_tb = {}  94     for k in pairs(obj) do
 95         table.insert(index_tb, k)  96     end
 97 
 98     local sort_func = function(a, b)  99         local type_a = type_value[type(a)] 100         local type_b = type_value[type(b)] 101         if type_a ~= type_b then
102             return type_a < type_b 103         else
104             if type_a == 1 or type_a == 2 then
105                 return a < b 106             elseif type_a == 5 then
107                 return tb.getlen(a) < tb.getlen(b) 108             else
109                 return false
110             end
111         end
112     end
113 
114     table.sort(index_tb, function(a, b) return sort_func(a, b) end) 115     return index_tb 116 end
117 
118 function tb.getlen(obj) 119     local count = 0
120     for k, v in pairs(obj) do
121         count = count + 1
122     end
123 
124     return count 125 end
126 
127 function tb.show(obj) 128     for k, v in pairs(obj) do
129         print(k, v) 130     end
131     local mt = getmetatable(obj) 132     if mt then
133         print("metatable", mt) 134     end
135 end
136 
137 return tb
Table Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM