[LuaArray] 嚴格的 Lua 數(shù)組實現(xiàn)

說點什么

由于 Lua table 的特殊構(gòu)造,使用純 Lua 實現(xiàn) 純數(shù)組 是很困難的—— tableArrayHashMap 的混合體, 二者合二為一,你中有我,我中有你,水乳交融,難以切分。因此, Array 很容易被污染而喪失 Array 的特性,HashMap 亦然。
table 還有一個奇妙的特性——元表,借助 元表 的特殊運用,我們可以實現(xiàn)一個比較嚴格的數(shù)組 Array。

基本要求

  1. 不可以存在非整數(shù)的索引;
  2. 可通過索引直接訪問元素;
  3. 傳入元素可直接創(chuàng)建一個數(shù)組,不傳默認為空數(shù)組;
  4. 不允許數(shù)組索引越界;
  5. 提供插入、移除、修改數(shù)組元素等基本操作數(shù)組的方法 (有需要的話,可以按需求增加數(shù)組操作的方法,這里主要提供的是實現(xiàn)機制和思想);
  6. 數(shù)組索引自動管理。

硬性約束

  1. 不允許直接通過索引對元素設值為nil,也就是如下形式的賦值是不允許的:
    arr[1] = nil;
    這是因為對索引元素設置nil 的話,數(shù)組的索引會被破壞;
  2. 為了秉承 Lua 的習慣,第一個元素的索引為 1。

實現(xiàn)

還是 table

首先,我們創(chuàng)建一個數(shù)組構(gòu)造函數(shù):

function Array(...)
    local __array__ = {...}
    -- do something to construct an array
end

通過 Array() 來構(gòu)造一個數(shù)組,其中 ... 是傳入的元素參數(shù),__array__ 用來存儲數(shù)組元素。
嗯哼,目前看來,__array__ 可不還是一個table嗎,其實還是什么都沒有做呀。
別急,接下來,重頭戲就來了。

變異的 table

為了構(gòu)造一個嚴格的數(shù)組,我們首要的問題就是保證 __array__ 存儲的都是整數(shù)的索引,這就要求我們就不能直接對 __array__ 進行操作,而要對它進行特殊保護——對外隱藏

function Array(...)
    local new_array = {}

    local __array__ = {...}

    local mt = {__index = new_array}
    setmetatable(new_array, mt)

    return new_array
end

為了實現(xiàn)隱藏的目的,我們增加了一個新的變量 new_array,讓其元表指向 __array__,這樣就可以直接通過索引訪問數(shù)組元素,而又達到了隱藏真實數(shù)組的目的。
但這樣還有兩個嚴重的安全隱患:

  1. 通過 getmetatable(new_array).__index 依然可以獲得真實的數(shù)組數(shù)據(jù);
  2. 最終返回的構(gòu)造數(shù)組將是 new_array,那么 new_array 必須具備數(shù)組的基本特性。

針對第一個問題,我們可以將__index 改為函數(shù),實現(xiàn)徹底隱藏 __array__的目的:

function Array(...)
    local new_array = {}

    local __array__ = {...}

    local mt = {
        __index = function(t, k)
            return __array__[k]
        end
    }
    setmetatable(new_array, mt)

    return new_array
end

現(xiàn)在我們來看第二個問題,new_array 將作為外交官,完成對真實數(shù)組 __array__ 的內(nèi)部操作,那么 new_array 就必須看起來像真實的數(shù)組。為了保證 new_array 的純凈,我們必須再次改造它的元表。

function Array(...)
    local new_array = {}

    local __array__ = {...}

    local mt = {
        __index = function(t, k)
            return __array__[k]
        end,
        __newindex = function(t, k, v)
            if nil == __array__[k] then
                print(string.format('warning : [%s] index out of range.', tostring(k)))
                return
            end
            if nil == v then
                print(string.format('warning : can not remove element by using  `nil`.'))
                return
            end
            __array__[k] = v
        end
    }
    setmetatable(new_array, mt)

    return new_array
end

我們對元表增加了 __newindex 的屬性,它控制著對 new_array 的內(nèi)部元素的附帶副作用的操作??梢钥闯?,目前我們只允許 new_array 修改 __array__ 已存在的元素的值 (且不允許對直接對元素設值為 nil,原因我們在 硬性約束中說過了),也就是所有與 __array__ 無關(guān)的操作都被過濾了。
到目前為止,new_array 已經(jīng)是一個較為嚴格的 數(shù)組 了,下面就要對這個 數(shù)組 進行擴展了,畢竟,除了能夠?qū)σ汛嬖诘脑剡M行賦值操作,現(xiàn)在的這個 數(shù)組 什么都做不了。

擴展 Array

為了擴展 Array 的操作屬性,必須添加額外的方法對 __array__ 進行操作,我們像構(gòu)造 __array__ 一樣,創(chuàng)建一個變量 __methods__ 用來保存方法列表,同樣,為了能夠訪問到 methods 中的方法,我們必須在元表中的 __index 添加 __methods__ 的訪問途徑 (在 web 中,這個概念是叫路由吧)

function Array(...)
    local new_array = {}

    local __array__ = {...}

    local __methods__ = {}
    function __methods__:insert(v, at)
        local len = #__array__ + 1
        at = type(at) == 'number' and at or len
        at = math.min(at, len)
        table.insert(__array__, at, v)
    end
    function __methods__:removeAt(at)
        at = type(at) == 'number' and at or #__array__
        table.remove(__array__, at)
    end
    function __methods__:print()
        print('---> array content begin  <---')
        for i, v in ipairs(__array__) do
            print(string.format('[%s] => ', i), v)
        end
        print('---> array content end  <---')
    end

    -- extend methods here

    local mt = {
        __index = function(t, k)
            if __array__[k] then
                return __array__[k]
            end
            if __methods__[k] then
                return __methods__[k]
            end
        end,
        __newindex = function(t, k, v)
            if nil == __array__[k] then
                print(string.format('warning : [%s] index out of range.', tostring(k)))
                return
            end
            if nil == v then
                print(string.format('warning : can not remove element by using  `nil`.'))
                return
            end
            __array__[k] = v
        end
    }
    setmetatable(new_array, mt)

    return new_array
end

這里,為了方便展示,僅寫了增、刪兩種方法,更多的還需要讀者自己補充。我們來做一些 測試

local arr = Array(1,2,3)
print(arr[1])   -- 1
print(arr[4])   -- nil
arr[1] = 4
arr:print()     -- 4,3,2
arr[4] = 'a'    -- warning : [4] index out of range.
arr[2] = nil    -- warning : can not remove element by using  `nil`.
arr:insert('a')
arr:insert('b', 2)
arr:print()     -- 4,b,2,3,a
arr:removeAt(1)
arr:print()     -- b,2,3,a

好了,現(xiàn)在一個較為嚴格的數(shù)組已經(jīng)完成了,怎么用,就看大家的意愿了。

PS: 借助同樣的機制,我們可以實現(xiàn)一個純 HashMap,懶得動,就不寫教程了吧。

參考:完整代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容