2

我嘗試高效地製作lua表格的副本。我寫了下面的函數copyTable(),它很好地工作(見下文)。但我想我可以使用功能的「傳值」機制來提高效率。我做了一些測試,以探索這個機制:Lua:有效地複製表格(深層複製)

function nop(x) 
    return x 
end 

function noop(x) 
    x={} 
    return x 
end 

function nooop(x) 
    x[#x+1]=4 
    return x 
end 

function copyTable(datatable) 
    local tblRes={} 
    if type(datatable)=="table" then 
    for k,v in pairs(datatable) do tblRes[k]=copyTable(v) end 
    else 
    tblRes=datatable 
    end 
    return tblRes 
end 

tab={1,2,3} 
print(tab)   -->table: 0x1d387e0 tab={1,2,3} 
print(nop(tab))  -->table: 0x1d387e0 tab={1,2,3} 
print(noop(tab))  -->table: 0x1e76f90 tab={1,2,3} 
print(nooop(tab))  -->table: 0x1d387e0 tab={1,2,3,4} 
print(tab)   -->table: 0x1d387e0 tab={1,2,3,4} 
print(copyTable(tab)) -->table: 0x1d388d0 

我們可以看出,表中的參數通過職能轉移不變(當我剛讀或添加的東西),除了空操作中(),我嘗試對現有的一個徹底的修改。

我看了Bas BossinkMichael Andersonthis Q/A的回答。關於傳遞或表作爲參數,他們強調了「由ref傳遞的參數」和「由值和表傳遞的參數是引用」之間的區別,以及出現這種區別的示例。

但是,這是什麼意思呢?我們是否有參考文獻的副本,但是由於數據指向並因此被操縱的數據仍然是相同的,不會被複制,所以與通過ref有什麼不同?當我們試圖影響表的nil時,noop()中的機制是特定的,這是爲了避免刪除表或在哪些情況下會觸發(我們可以用nooop()看到,並不總是如此表被修改)?

我的問題:如何通過表真正起作用的機制?有沒有辦法讓一個更有效的方式來複製表的數據沒有我的copyTable的負擔?

+0

遞歸和尾調用。這就和你一樣高效。 – warspyking

+0

@warspyking:我會在太空中獲得更多速度嗎?當我需要組裝子表時,我應該如何「符合」尾部呼叫? – user1771398

+0

https://www.lua.org/pil/6.3.html – warspyking

回答

1

傳入的Lua參數的規則是類似於C:一切是由值通,但表和用戶數據被作爲指針周圍通過。傳遞引用的副本在使用上看起來沒有太多不同,但與傳遞引用完全不同。

例如,您特別帶來了這部分了。

function noop(x) 
    x={} 
    return x 
end 
print(noop(tab))  -->table: 0x1e76f90 tab={1, 2, 3} 

要指定爲新表[1]到變量x的值(x現在持有新的指針值)。您沒有改變原始表格,tab變量仍將指針值保留爲原始表格。當您從noop返回時,您將傳回新表的值,該值爲空。 變量保存值,指針是一個值,而不是引用。

編輯:

錯過您的其他問題。不,如果你想深度複製一張表,一個類似於你寫的功能是唯一的方法。深度拷貝是非常慢當表變大時。爲了避免出現性能問題,您可以使用像「倒帶表」,用於跟蹤向他們提出這樣他們就可以在稍後的時間點(遞歸非常有用的回溯與上下文)撤消改變的機制。或者,如果您只是需要防止用戶使用表內部元件,請編寫「可凍結」特性。

[1]試想{}語法是構造一個新的表,並返回一個指向新表的功能。

+0

然後,我明白了「通過ref傳遞的參數」和「通過值和表傳遞的參數是引用」之間的區別:只要我使用指針,通過例如讀取x.foo或者通過影響x.foo =「foo」,所有內容與通過ref傳遞的內容非常相似。與ref的區別是,我可以重新影響x,例如x = {},這是我無法用引用做的事情。正確嗎? – user1771398

+0

這絕對正確。引用是*變量*的別名,而不是保存相同值的變量。例如,在C++中,由於這一事實,您不能將新對象分配給引用。但是如果你使用了指針,你可以改變指針,因爲指針是值。這是一個微妙的差異,但一個有用的。 Lua使用指針語義。 – ktb

0

如果您確信這3個假設(A)是有效的 「標籤」(該表被複制):

  1. 有沒有表鍵

    t1 = {} 
    tab = {} 
    tab[t1] = value 
    
  2. 有沒有重複的表值

    t1 = {} 
    tab = {} 
    tab.a = t1 
    tab.b = t1 
    -- or 
    -- tab.a.b...x = t1 
    
  3. 沒有遞歸表:

    tab = {} 
    tab.a = tab 
    -- or 
    -- tab.a.b...x = tab 
    

然後,你所提供的代碼是最小的,幾乎儘可能高效。

如果A1不成立(即你有表鍵),則必須更改您的代碼:

function copyTable(datatable) 
    local tblRes={} 
    if type(datatable)=="table" then 
    for k,v in pairs(datatable) do 
     tblRes[copyTable(k)] = copyTable(v) 
    end 
    else 
    tblRes=datatable 
    end 
    return tblRes 
end 

如果A2不成立(即你有反覆表值),那麼你可能會將您的代碼更改爲:

function copyTable(datatable, cache) 
    cache = cache or {} 
    local tblRes={} 
    if type(datatable)=="table" then 
    if cache[datatable] then return cache[datatable] 
    for k,v in pairs(datatable) do 
     tblRes[copyTable(k, cache)] = copyTable(v, cache) 
    end 
    cache[datatable] = tblRes 
    else 
    tblRes=datatable 
    end 
    return tblRes 
end 

但是,如果您有許多重複的大型表,這種方法只能回報。因此,評估實際生產場景的哪個版本更快就是一個問題。

如果A3不成立(即您有遞歸表),那麼您的代碼(以及上面的兩個調整)將進入無限遞歸循環並最終引發堆棧溢出。

來處理最簡單的方法是保持一個回溯,如果表遞歸發生拋出一個錯誤:

function copyTable(datatable, cache, parents) 
    cache = cache or {} 
    parents = parents or {} 
    local tblRes={} 
    if type(datatable)=="table" then 
    if cache[datatable] then return cache[datatable] 
    assert(not parents[datatable]) 
    parents[datatable] = true 
    for k,v in pairs(datatable) do 
     tblRes[copyTable(k, cache, parents)] 
     = copyTable(v, cache, parents) 
    end 
    parents[datatable] = false 
    cache[datatable] = tblRes 
    else 
    tblRes=datatable 
    end 
    return tblRes 
end 

我的deepcopy的功能,處理遞歸表,保留原始結構的解決方案可以在這裏找到: https://gist.github.com/cpeosphoros/0aa286c6b39c1e452d9aa15d7537ac95

+0

有趣的是有一個「開箱即用」的功能來在各種環境下執行深度複製。你能否解釋一下函數deepCopy(value,cache,promises,copies)或/和什麼是變量以及一個可用的例子? – user1771398