5

可随机的集合对象

 3 years ago
source link: https://zhuanlan.zhihu.com/p/275102818
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

可随机的集合对象

下面的Lua代码实现了一个很实用的容器:可随机的集合。

首先它是一个集合,里面的元素不会重复,且增加删除都是O(1)的复杂度。其次它是可随机的,即可以从中随机选择N个元素出来,这个用普通的table就没有办法实现。

代码如下:

---
--- 随机集合对象
--- Created by colin.
---

local tinsert = table.insert
local tremove = table.remove
local tmove = table.move
local tconcat = table.concat
local mrandom = math.random
local ipairs = ipairs
local pairs = pairs
local tostring = tostring

---@class RandSet
---@field list table
---@field idxs table
local RandSet = {}

local RandSetMT = {
    __index = RandSet,
    __pairs = function(t)
        local itr = function(t, k)
            return next(t.idxs, k)
        end
        return itr, t, nil
    end,
    __len = function(t)
        return #t.list
    end,
    __tostring  = function(t)
        return string.format("RandSet: (%s)", RandSet.concat(t, ", "))
    end
}

---增加元素
---@return boolean 是否增加成功
function RandSet:add(e)
    if self.idxs[e] then
        return false
    else
        tinsert(self.list, e)
        self.idxs[e] = #self.list
        return true
    end
end

---删除元素
function RandSet:remove(e)
    local idx = self.idxs[e]
    if idx then
        local ln = #self.list
        if idx > 0 and idx <= ln then
            local re
            if idx == ln then
                re = tremove(self.list)
                self.idxs[re] = nil
            else
                re = self.list[idx]
                self.idxs[re] = nil
                local le = tremove(self.list)
                self.list[idx] = le
                self.idxs[le] = idx
            end
        end
    end
end

---判断集合中是否存在元素e
---@return boolean 是否存在
function RandSet:has(e)
    return self.idxs[e] ~= nil
end

---从集合随机选择n个元素返回,如果n为nil,则返回元素,否则返回元素列表
---@return any|any[]
function RandSet:choice(n)
    local ln = #self.list
    if not n then
        return self.list[mrandom(1, ln)]
    elseif n >= ln then
        return tmove(self.list, 1, ln, 1, {})
    else
        local s = 1
        local i, ei, es
        for _ = 1, n do
            i = mrandom(s, ln)
            ei = self.list[i]
            es = self.list[s]
            self.list[s] = ei
            self.idxs[ei] = s
            self.list[i] = es
            self.idxs[es] = i
            s = s + 1
        end
        return tmove(self.list, 1, s -1, 1, {})
    end
end

---将集合中的元素连接成字符串返回
---@param sep string 分隔符
---@param i number 起始索引,默认为1
---@param j number 结束索引,默认为#set
---@return string
function RandSet:concat(sep, i, j)
    i = i or 1
    j = j or #self.list
    local t = {}
    for _, e in ipairs(self.list) do
        t[#t+1] = tostring(e)
    end
    return tconcat(t, sep)
end

---新建一个集合对象返回
---@return RandSet
function RandSet.new()
    return setmetatable({
        list = {},
        idxs = {},
    }, RandSetMT)
end

-------------------------------------------------------------------
local function test()
    local set = RandSet.new()
    print("add---------------")
    for i = 1, 10 do
        set:add(i)
    end
    print(set)
    print("len: ", #set)
    print("remove---------------")
    set:remove(8)
    set:remove(7)
    set:remove(3)
    print(set)
    print("has---------------")
    print(set:has(8), set:has(1), set:has(10))
    print("choice---------------")
    print(set:choice())
    print(tconcat(set:choice(2), ", "))
    print(tconcat(set:choice(5), ", "))
    print(tconcat(set:choice(10), ", "))
    print(tconcat(set:choice(0), ", "))
    print("for---------------")
    for e in pairs(set) do
        print(e)
    end
end
--test()
-------------------------------------------------------------------

return RandSet

这个容器在某些随机推荐系统中特别有用。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK