跳转到内容

模組:沙盒/a2569875/PythonScript/Librarys/Random

维基百科,自由的百科全书
-- Module:PythonScript/Librarys/Random
local RandomMod = {}

local PyClass = require("Module:沙盒/a2569875/PythonScript/Class")
local Runtime = require("Module:沙盒/a2569875/PythonScript/Runtime")

-- 內部輔助函式:將 Python 物件轉為 Lua 數值
local function unwrap_num(obj)
    if type(obj) == "table" and obj.__type == "instance" and obj.__builtin_value ~= nil then
        obj = obj.__builtin_value
    end
    if type(obj) == "boolean" then return obj and 1 or 0 end
    if type(obj) == "table" and obj.__type == "int" and obj.is_big then
        local MathCore = require("Module:沙盒/a2569875/PythonScript/Runtime/Cmath")
        return tonumber(MathCore.BigInt.to_string(obj.sign, obj.limbs))
    end
    return obj
end

-- 內部輔助函式:提取 Python 序列元素
local function extract_elements(seq)
    if type(seq) == "table" and (seq.__type == "list" or seq.__type == "tuple") then return seq.elements end
    local Evaluator = require("Module:沙盒/a2569875/PythonScript/Evaluator")
    local it = Runtime.builtins.iter.__call({seq})
    local next_fn = PyClass.get_attr(it, "__next__")
    local res = {}
    while true do
        local ok, val = pcall(Evaluator.invoke, next_fn.__func, {}, {}, next_fn.__self)
        if not ok then break end
        table.insert(res, val)
    end
    return res
end

-- LCG (線性同餘生成器) 核心參數,保證不超出 Lua 53-bit 安全整數範圍
local LCG_A = 1664525
local LCG_C = 1013904223
local LCG_M = 4294967296

-- 共用核心方法字典
local Random_dict = {}

-- 取得 [0.0, 1.0) 亂數的快捷閉包
local function get_rand(self)
    return self.__dict._rand_func()
end

Random_dict.__init__ = {
    __type = "builtin_method",
    __call = function(args, kwargs)
        local self, x = args[1], args[2]
        self.__dict._is_global = false
        self.__dict._state = 0
        self.__dict._gauss_next = nil
        
        self.__dict._rand_func = function()
            self.__dict._state = (self.__dict._state * LCG_A + LCG_C) % LCG_M
            return self.__dict._state / LCG_M
        end
        
        local Evaluator = require("Module:沙盒/a2569875/PythonScript/Evaluator")
        Evaluator.invoke(PyClass.get_attr(self, "seed"), {x}, {})
        return nil
    end
}

Random_dict.seed = {
    __type = "builtin_method",
    __call = function(args)
        local self, a = args[1], args[2]
        local seed_val = 0
        if a ~= nil and a ~= Runtime.builtins.None then
            if type(a) == "number" then seed_val = math.floor(a)
            elseif type(a) == "string" then
                for i = 1, #a do seed_val = (seed_val * 31 + string.byte(a, i)) % LCG_M end
            else
                seed_val = unwrap_num(a)
            end
        else
            seed_val = math.floor(os.time() + os.clock() * 1000000)
        end
        
        if self.__dict._is_global then
            math.randomseed(seed_val)
        else
            self.__dict._state = seed_val % LCG_M
        end
        self.__dict._gauss_next = nil
        return Runtime.builtins.None
    end
}

Random_dict.random = {
    __type = "builtin_method",
    __call = function(args) return get_rand(args[1]) end
}

Random_dict.uniform = {
    __type = "builtin_method",
    __call = function(args)
        local self, a, b = args[1], unwrap_num(args[2]), unwrap_num(args[3])
        return a + (b - a) * get_rand(self)
    end
}

Random_dict.randint = {
    __type = "builtin_method",
    __call = function(args)
        local self, a, b = args[1], unwrap_num(args[2]), unwrap_num(args[3])
        if b < a then Runtime.raise("ValueError", "empty range for randint") end
        local val = a + math.floor(get_rand(self) * (b - a + 1))
        return Runtime.builtins.int.__call({val})
    end
}

Random_dict.randrange = {
    __type = "builtin_method",
    __call = function(args)
        local self = args[1]
        local start, stop, step = 0, nil, 1
        if #args == 2 then stop = unwrap_num(args[2])
        elseif #args == 3 then start = unwrap_num(args[2]); stop = unwrap_num(args[3])
        elseif #args == 4 then start = unwrap_num(args[2]); stop = unwrap_num(args[3]); step = unwrap_num(args[4])
        else Runtime.raise("TypeError", "randrange expected 1 to 3 arguments") end
        
        if step == 0 then Runtime.raise("ValueError", "zero step for randrange()") end
        local width = stop - start
        if (step > 0 and width <= 0) or (step < 0 and width >= 0) then Runtime.raise("ValueError", "empty range for randrange()") end
        
        local n = math.floor((width + step - (step > 0 and 1 or -1)) / step)
        local val = start + step * math.floor(get_rand(self) * n)
        return Runtime.builtins.int.__call({val})
    end
}

Random_dict.choice = {
    __type = "builtin_method",
    __call = function(args)
        local self, seq = args[1], args[2]
        local elts = extract_elements(seq)
        if #elts == 0 then Runtime.raise("IndexError", "Cannot choose from an empty sequence") end
        local idx = math.floor(get_rand(self) * #elts) + 1
        return elts[idx]
    end
}

Random_dict.choices = {
    __type = "builtin_method",
    __call = function(args, kwargs)
        local self, pop = args[1], args[2]
        local kw = kwargs or {}
        local weights = kw.weights or (args[3] and args[3] ~= Runtime.builtins.None and args[3] or nil)
        local cum_weights = kw.cum_weights
        local k_val = kw.k or 1
        
        local pop_elts = extract_elements(pop)
        local n = #pop_elts
        if n == 0 then Runtime.raise("IndexError", "Cannot choose from an empty sequence") end
        
        local cw = {}
        if cum_weights ~= nil and cum_weights ~= Runtime.builtins.None then
            if weights ~= nil then Runtime.raise("TypeError", "Cannot specify both weights and cumulative weights") end
            local cw_elts = extract_elements(cum_weights)
            if #cw_elts ~= n then Runtime.raise("ValueError", "The number of weights does not match the population") end
            for i = 1, n do cw[i] = unwrap_num(cw_elts[i]) end
        elseif weights ~= nil and weights ~= Runtime.builtins.None then
            local w_elts = extract_elements(weights)
            if #w_elts ~= n then Runtime.raise("ValueError", "The number of weights does not match the population") end
            local sum = 0
            for i = 1, n do sum = sum + unwrap_num(w_elts[i]); cw[i] = sum end
        end
        
        local res = {}
        if #cw > 0 then
            local total = cw[n]
            if total <= 0 then Runtime.raise("ValueError", "Total of weights must be greater than zero") end
            for _ = 1, k_val do
                local r = get_rand(self) * total
                local lo, hi = 1, n
                while lo < hi do
                    local mid = math.floor((lo + hi) / 2)
                    if cw[mid] < r then lo = mid + 1 else hi = mid end
                end
                table.insert(res, pop_elts[lo])
            end
        else
            for _ = 1, k_val do
                local idx = math.floor(get_rand(self) * n) + 1
                table.insert(res, pop_elts[idx])
            end
        end
        return { __type = "list", elements = res }
    end
}

Random_dict.sample = {
    __type = "builtin_method",
    __call = function(args)
        local self, pop, k = args[1], args[2], unwrap_num(args[3])
        local pop_elts = extract_elements(pop)
        local n = #pop_elts
        if k < 0 or k > n then Runtime.raise("ValueError", "Sample larger than population or is negative") end
        local res = {}
        local pool = {}
        for i = 1, n do pool[i] = pop_elts[i] end
        for i = 1, k do
            local j = math.floor(get_rand(self) * (n - i + 1)) + i
            res[i] = pool[j]
            pool[j] = pool[i]
        end
        return { __type = "list", elements = res }
    end
}

Random_dict.shuffle = {
    __type = "builtin_method",
    __call = function(args)
        local self, seq = args[1], args[2]
        if type(seq) ~= "table" or seq.__type ~= "list" then Runtime.raise("TypeError", "shuffle() only accepts mutable sequences like list") end
        local elts = seq.elements
        for i = #elts, 2, -1 do
            local j = math.floor(get_rand(self) * i) + 1
            elts[i], elts[j] = elts[j], elts[i]
        end
        return Runtime.builtins.None
    end
}

Random_dict.triangular = {
    __type = "builtin_method",
    __call = function(args)
        local self, low, high = args[1], unwrap_num(args[2] or 0), unwrap_num(args[3] or 1)
        local mode = args[4] ~= nil and unwrap_num(args[4]) or (low + high) / 2.0
        local u = get_rand(self)
        local c = (mode - low) / (high - low)
        if u > c then
            u = 1.0 - u; c = 1.0 - c
            low, high = high, low
        end
        return low + (high - low) * math.sqrt(u * c)
    end
}

Random_dict.gauss = {
    __type = "builtin_method",
    __call = function(args)
        local self, mu, sigma = args[1], unwrap_num(args[2]), unwrap_num(args[3])
        local z = self.__dict._gauss_next
        self.__dict._gauss_next = nil
        if not z then
            local x2pi = get_rand(self) * math.pi * 2
            local g2rad = math.sqrt(-2.0 * math.log(1.0 - get_rand(self)))
            z = math.cos(x2pi) * g2rad
            self.__dict._gauss_next = math.sin(x2pi) * g2rad
        end
        return mu + z * sigma
    end
}

Random_dict.normalvariate = Random_dict.gauss

Random_dict.expovariate = {
    __type = "builtin_method",
    __call = function(args)
        local self, lambd = args[1], unwrap_num(args[2])
        return -math.log(1.0 - get_rand(self)) / lambd
    end
}

Random_dict.gammavariate = {
    __type = "builtin_method",
    __call = function(args)
        local self, alpha, beta = args[1], unwrap_num(args[2]), unwrap_num(args[3])
        if alpha <= 0.0 or beta <= 0.0 then Runtime.raise("ValueError", "gammavariate: alpha and beta must be > 0.0") end
        local Evaluator = require("Module:沙盒/a2569875/PythonScript/Evaluator")
        if alpha > 1.0 then
            local d = alpha - 1.0 / 3.0
            local c = 1.0 / math.sqrt(9.0 * d)
            while true do
                local x = Evaluator.invoke(Random_dict.gauss.__call, {self, 0.0, 1.0}, {})
                local v = 1.0 + c * x
                if v > 0.0 then
                    v = v * v * v
                    local u = get_rand(self)
                    local x2 = x * x
                    if u < 1.0 - 0.0331 * x2 * x2 or math.log(u) < 0.5 * x2 + d * (1.0 - v + math.log(v)) then
                        return d * v * beta
                    end
                end
            end
        else
            local u = get_rand(self)
            local x = Evaluator.invoke(Random_dict.gammavariate.__call, {self, 1.0 + alpha, 1.0}, {})
            return x * math.pow(u, 1.0 / alpha) * beta
        end
    end
}

Random_dict.betavariate = {
    __type = "builtin_method",
    __call = function(args)
        local self, alpha, beta = args[1], unwrap_num(args[2]), unwrap_num(args[3])
        local Evaluator = require("Module:沙盒/a2569875/PythonScript/Evaluator")
        local y = Evaluator.invoke(Random_dict.gammavariate.__call, {self, alpha, 1.0}, {})
        if y == 0.0 then return 0.0 end
        return y / (y + Evaluator.invoke(Random_dict.gammavariate.__call, {self, beta, 1.0}, {}))
    end
}

Random_dict.paretovariate = {
    __type = "builtin_method",
    __call = function(args)
        local self, alpha = args[1], unwrap_num(args[2])
        return 1.0 / math.pow(1.0 - get_rand(self), 1.0 / alpha)
    end
}

Random_dict.weibullvariate = {
    __type = "builtin_method",
    __call = function(args)
        local self, alpha, beta = args[1], unwrap_num(args[2]), unwrap_num(args[3])
        return alpha * math.pow(-math.log(1.0 - get_rand(self)), 1.0 / beta)
    end
}

Random_dict.lognormvariate = {
    __type = "builtin_method",
    __call = function(args)
        local self = args[1]
        local Evaluator = require("Module:沙盒/a2569875/PythonScript/Evaluator")
        return math.exp(Evaluator.invoke(Random_dict.gauss.__call, args, {}))
    end
}

Random_dict.vonmisesvariate = {
    __type = "builtin_method",
    __call = function(args)
        local self, mu, kappa = args[1], unwrap_num(args[2]), unwrap_num(args[3])
        if kappa <= 1e-6 then return math.pi * 2.0 * get_rand(self) end
        local a = 1.0 + math.sqrt(1.0 + 4.0 * kappa * kappa)
        local b = (a - math.sqrt(2.0 * a)) / (2.0 * kappa)
        local r = (1.0 + b * b) / (2.0 * b)
        while true do
            local u1 = get_rand(self)
            local z = math.cos(math.pi * u1)
            local f = (1.0 + r * z) / (r + z)
            local c = kappa * (r - f)
            local u2 = get_rand(self)
            if u2 < c * (2.0 - c) or u2 <= c * math.exp(1.0 - c) then
                local u3 = get_rand(self)
                local res = mu + ((u3 > 0.5) and math.acos(f) or -math.acos(f))
                return res % (2.0 * math.pi)
            end
        end
    end
}

Random_dict.getrandbits = {
    __type = "builtin_method",
    __call = function(args)
        local self, k = args[1], unwrap_num(args[2])
        if k <= 0 or math.floor(k) ~= k then Runtime.raise("ValueError", "number of bits must be greater than zero") end
        if k > 100000 then Runtime.raise("ValueError", "too many bits") end
        
        local bin_parts = {}
        for _ = 1, k do table.insert(bin_parts, get_rand(self) < 0.5 and "0" or "1") end
        return Runtime.builtins.int.__call({ "0b" .. table.concat(bin_parts), 2 })
    end
}

Random_dict.randbytes = {
    __type = "builtin_method",
    __call = function(args)
        local self, n = args[1], unwrap_num(args[2])
        if n < 0 or math.floor(n) ~= n then Runtime.raise("ValueError", "number of bytes must be non-negative integer") end
        if n > 1000000 then Runtime.raise("ValueError", "too many bytes") end
        
        local bytes = {}
        for _ = 1, n do table.insert(bytes, string.char(math.floor(get_rand(self) * 256))) end
        return { __type = "bytes", _data = table.concat(bytes) }
    end
}

local RandomClass = PyClass.make_class("Random", {}, Random_dict)

function RandomMod.get_module()
    -- 建立全域的虛擬 Random 實例,其背後直接對接 Lua 的 `math.random` 來發揮最大效能
    local global_inst = PyClass.make_instance(RandomClass)
    global_inst.__dict._is_global = true
    global_inst.__dict._gauss_next = nil
    global_inst.__dict._rand_func = function() return math.random() end
    
    local Evaluator = require("Module:沙盒/a2569875/PythonScript/Evaluator")
    Evaluator.invoke(PyClass.get_attr(global_inst, "seed"), {}, {})
    
    -- 將 Random_dict 的所有方法直接對應綁定至模組全域
    local mod_dict = {
        Random = RandomClass,
        SystemRandom = {
            __type = "builtin_method",
            __call = function() Runtime.raise("NotImplementedError", "SystemRandom is not available in MediaWiki environment") end
        }
    }
    
    for k, v in pairs(Random_dict) do
        if k ~= "__init__" and k ~= "__new__" then
            mod_dict[k] = { __type = "bound_method", __self = global_inst, __func = v }
        end
    end
    
    local mod = PyClass.make_instance(PyClass.make_class("module", {}, mod_dict))
    mod.__dict["__name__"] = "random"
    return mod
end

return RandomMod