[LuaArray] 严格的 Lua 数组实现

说点什么

由于 Lua table 的特殊构造,使用纯 Lua 实现 纯数组 是很困难的—— tableArrayHashMap 的混合体, 二者合二为一,你中有我,我中有你,水乳交融,难以切分。因此, Array 很容易被污染而丧失 Array 的特性,HashMap 亦然。
table 还有一个奇妙的特性——元表,借助 元表 的特殊运用,我们可以实现一个比较严格的数组 Array

基本要求

  1. 不可以存在非整数的索引;
  2. 可通过索引直接访问元素;
  3. 传入元素可直接创建一个数组,不传默认为空数组;
  4. 不允许数组索引越界;
  5. 提供插入、移除、修改数组元素等基本操作数组的方法 (有需要的话,可以按需求增加数组操作的方法,这里主要提供的是实现机制和思想)
  6. 数组索引自动管理。

硬性约束

  1. 不允许直接通过索引对元素设值为nil,也就是如下形式的赋值是不允许的:
    arr[1] = nil;
    这是因为对索引元素设置nil 的话,数组的索引会被破坏;
  2. 为了秉承 Lua 的习惯,第一个元素的索引为 1

实现

还是 table

首先,我们创建一个数组构造函数:

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

通过 Array() 来构造一个数组,其中 ... 是传入的元素参数,__array__ 用来存储数组元素。
嗯哼,目前看来,__array__ 可不还是一个table吗,其实还是什么都没有做呀。
别急,接下来,重头戏就来了。

变异的 table

为了构造一个严格的数组,我们首要的问题就是保证 __array__ 存储的都是整数的索引,这就要求我们就不能直接对 __array__ 进行操作,而要对它进行特殊保护——对外隐藏

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

    local __array__ = {...}

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

    return new_array
end

为了实现隐藏的目的,我们增加了一个新的变量 new_array,让其元表指向 __array__,这样就可以直接通过索引访问数组元素,而又达到了隐藏真实数组的目的。
但这样还有两个严重的安全隐患:

  1. 通过 getmetatable(new_array).__index 依然可以获得真实的数组数据;
  2. 最终返回的构造数组将是 new_array,那么 new_array 必须具备数组的基本特性。

针对第一个问题,我们可以将__index 改为函数,实现彻底隐藏 __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

现在我们来看第二个问题,new_array 将作为外交官,完成对真实数组 __array__ 的内部操作,那么 new_array 就必须看起来像真实的数组。为了保证 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 的内部元素的附带副作用的操作。可以看出,目前我们只允许 new_array 修改 __array__ 已存在的元素的值 (且不允许对直接对元素设值为 nil,原因我们在 硬性约束中说过了),也就是所有与 __array__ 无关的操作都被过滤了。
到目前为止,new_array 已经是一个较为严格的 数组 了,下面就要对这个 数组 进行扩展了,毕竟,除了能够对已存在的元素进行赋值操作,现在的这个 数组 什么都做不了。

扩展 Array

为了扩展 Array 的操作属性,必须添加额外的方法对 __array__ 进行操作,我们像构造 __array__ 一样,创建一个变量 __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

好了,现在一个较为严格的数组已经完成了,怎么用,就看大家的意愿了。

PS: 借助同样的机制,我们可以实现一个纯 HashMap,懒得动,就不写教程了吧。

参考:完整代码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容