Module:Random/sandbox explained

-- This module contains a number of functions that make use of random numbers.

local cfg =

---------------------------------------------------------------------------------------- Configuration--------------------------------------------------------------------------------------

-- Set this to true if your wiki has a traffic rate of less than one edit every two minutes or so.-- This will prevent the same "random" number being generated many times in a row until a new edit is made-- to the wiki. This setting is only relevant if the |same= parameter is set.cfg.lowTraffic = false

-- If cfg.lowTraffic is set to true, and the |same= parameter is set, this value is used for the refresh rate of the random seed.-- This is the number of seconds until the seed is changed. Getting this right is tricky. If you set it too high, the same number-- will be returned many times in a row. If you set it too low, you may get different random numbers appearing on the same page,-- particularly for pages that take many seconds to process.cfg.seedRefreshRate = 60

---------------------------------------------------------------------------------------- End configuration--------------------------------------------------------------------------------------

local p = -- For functions available from other Lua modules.local l = -- For functions not available from other Lua modules, but that need to be accessed using table keys.

local yesno = require('Module:Yesno')local makeList = require('Module:List').makeList

---------------------------------------------------------------------------------------- Helper functions--------------------------------------------------------------------------------------

local function raiseError(msg) -- This helps to generate a wikitext error. It is the calling function's responsibility as to how to include it in the output. return mw.ustring.format(' error: %s.', msg)end

---------------------------------------------------------------------------------------- random number function--------------------------------------------------------------------------------------

local function getBigRandom(l, u) -- Gets a random integer between l and u, and is not limited to RAND_MAX. local r = 0 local n = 2^math.random(30) -- Any power of 2. local limit = math.ceil(53 / (math.log(n) / math.log(2))) for i = 1, limit do r = r + math.random(0, n - 1) / (n^i) end return math.floor(r * (u - l + 1)) + lend

function l.number(args) -- Gets a random number. first = tonumber(args[1]) second = tonumber(args[2]) -- This needs to use if statements as math.random won't accept explicit nil values as arguments. if first then if second then if first > second then -- Second number cannot be less than the first, or it causes an error. first, second = second, first end return getBigRandom(first, second) else return getBigRandom(1, first) end else return math.random endend

---------------------------------------------------------------------------------------- Date function--------------------------------------------------------------------------------------

function l.date(args) -- This function gets random dates, and takes timestamps as positional arguments. -- With no arguments specified, it outputs a random date in the current year. -- With two arguments specified, it outputs a random date between the timestamps. -- With one argument specified, the date is a random date between the unix epoch (1 Jan 1970) and the timestamp. -- The output can be formatted using the "format" argument, which works in the same way as the #time parser function. -- The default format is the standard Wikipedia timestamp. local lang = mw.language.getContentLanguage

local function getDate(format, ts) local success, date = pcall(lang.formatDate, lang, format, ts) if success then return date end end

local function getUnixTimestamp(ts) local unixts = getDate('U', ts) if unixts then return tonumber(unixts) end end

local t1 = args[1] local t2 = args[2] -- Find the start timestamp and the end timestamp. local startTimestamp, endTimestamp if not t1 then -- Find the first and last second in the current year. local currentYear = tonumber(getDate('Y')) local currentYearStartUnix = tonumber(getUnixTimestamp('1 Jan ' .. tostring(currentYear))) local currentYearEndUnix = tonumber(getUnixTimestamp('1 Jan ' .. tostring(currentYear + 1))) - 1 startTimestamp = '@' .. tostring(currentYearStartUnix) -- @ is used to denote Unix timestamps with lang:formatDate. endTimestamp = '@' .. tostring(currentYearEndUnix) elseif not t2 then startTimestamp = '@0' -- the Unix epoch, 1 January 1970 endTimestamp = t1 else startTimestamp = t1 endTimestamp = t2 end

-- Get Unix timestamps and return errors for bad input (or for bugs in the underlying PHP library, of which there are unfortunately a few) local startTimestampUnix = getUnixTimestamp(startTimestamp) local endTimestampUnix = getUnixTimestamp(endTimestamp) if not startTimestampUnix then return raiseError('"' .. tostring(startTimestamp) .. '" was not recognised as a valid timestamp') elseif not endTimestampUnix then return raiseError('"' .. tostring(endTimestamp) .. '" was not recognised as a valid timestamp') elseif startTimestampUnix > endTimestampUnix then return raiseError('the start date must not be later than the end date (start date: "' .. startTimestamp .. '", end date: "' .. endTimestamp .. '")') end

-- Get a random number between the two Unix timestamps and return it using the specified format. local randomTimestamp = getBigRandom(startTimestampUnix, endTimestampUnix) local dateFormat = args.format or 'H:i, d F Y (T)' local result = getDate(dateFormat, '@' .. tostring(randomTimestamp)) if result then return result else return raiseError('"' .. dateFormat .. '" is not a valid date format') endend

---------------------------------------------------------------------------------------- List functions--------------------------------------------------------------------------------------

local function randomizeArray(t, limit) -- Randomizes an array. It works by iterating through the list backwards, each time swapping the entry -- "i" with a random entry. Courtesy of Xinhuan at http://forums.wowace.com/showthread.php?p=279756 -- If the limit parameter is set, the array is shortened to that many elements after being randomized. -- The lowest possible value is 0, and the highest possible is the length of the array. local len = #t for i = len, 2, -1 do local r = math.random(i) t[i], t[r] = t[r], t[i] end if limit and limit < len then local ret = for i, v in ipairs(t) do if i > limit then break end ret[i] = v end return ret else return t endend

local function removeBlanks(t) -- Removes blank entries from an array so that it can be used with ipairs. local ret = for k, v in pairs(t) do if type(k)

'number' then table.insert(ret, k) end end table.sort(ret) for i, v in ipairs(ret) do ret[i] = t[v] end return retend

local function makeSeparator(sep) if sep

'space' then -- Include an easy way to use spaces as separators. return ' ' elseif sep

'newline' then -- Ditto for newlines return '\n' elseif type(sep)

'string' then -- If the separator is a recognised MediaWiki separator, use that. Otherwise use the value of sep if it is a string. local mwseparators = for _, mwsep in ipairs(mwseparators) do if sep

mwsep then return mw.message.new(sep .. '-separator'):plain end end return sep endend

local function makeRandomList(args) local list = removeBlanks(args) list = randomizeArray(list, tonumber(args.limit)) return listend

function l.item(args) -- Returns a random item from a numbered list. local list = removeBlanks(args) local len = #list if len >= 1 then return list[math.random(len)] endend

function l.list(args) -- Randomizes a list and concatenates the result with a separator. local list = makeRandomList(args) local sep = makeSeparator(args.sep or args.separator) return table.concat(list, sep)end

function l.text_list(args) -- Randomizes a list and concatenates the result, text-style. Accepts separator and conjunction arguments. local list = makeRandomList(args) local sep = makeSeparator(args.sep or args.separator) local conj = makeSeparator(args.conj or args.conjunction) return mw.text.listToText(list, sep, conj)end

function l.array(args) -- Returns a Lua array, randomized. For use from other Lua modules. return randomizeArray(args.t, args.limit)end

---------------------------------------------------------------------------------------- HTML list function--------------------------------------------------------------------------------------

function l.html_list(args, listType) -- Randomizes a list and turns it into an HTML list. Uses . listType = listType or 'bulleted' local listArgs = makeRandomList(args) -- Arguments for . for k, v in pairs(args) do if type(k)

'string' then listArgs[k] = v end end return makeList(listType, listArgs)end

---------------------------------------------------------------------------------------- The main function. Called from other Lua modules.--------------------------------------------------------------------------------------

function p.main(funcName, args, listType) -- Sets the seed for the random number generator and passes control over to the other functions. local same = yesno(args.same) if not same then -- Generates a different number every time the module is called, even from the same page. -- This is because of the variability of os.clock (the time in seconds that the Lua script has been running for). math.randomseed(mw.site.stats.edits + mw.site.stats.pages + os.time + math.floor(os.clock * 1000000000)) elseif not cfg.lowTraffic then -- Make the seed as random as possible without using anything time-based. This means that the same random number -- will be generated for the same input from the same page - necessary behaviour for some wikicode templates that -- assume bad pseudo-random-number generation. local stats = mw.site.stats local views = stats.views or 0 -- This is not always available, so we need a backup. local seed = views + stats.pages + stats.articles + stats.files + stats.edits + stats.users + stats.activeUsers + stats.admins -- Make this as random as possible without using os.time or os.clock math.randomseed(seed) else -- Make the random seed change every n seconds, where n is set by cfg.seedRefreshRate. -- This is useful for low-traffic wikis where new edits may not happen very often. math.randomseed(math.floor(os.time / cfg.seedRefreshRate)) end assert(type(args)

'table', 'the second argument to p.main must be a table') return l[funcName](args, listType)end ---------------------------------------------------------------------------------------- Process arguments from #invoke--------------------------------------------------------------------------------------

local function makeWrapper(funcName, listType) -- This function provides a wrapper for argument-processing from #invoke. -- listType is only used with p.html_list, and is nil the rest of the time. return function (frame) -- If called via #invoke, use the args passed into the invoking template, or the args passed to #invoke if any exist. -- Otherwise assume args are being passed directly in from the debug console or from another Lua module. local origArgs if frame

mw.getCurrentFrame then origArgs = frame:getParent.args for k, v in pairs(frame.args) do origArgs = frame.args break end else origArgs = frame end -- Trim whitespace and remove blank arguments. local args = for k, v in pairs(origArgs) do if type(v)

'string' then v = mw.text.trim(v) end if v ~= then args[k] = v end end return p.main(funcName, args, listType) endend

-- Process arguments for HTML list functions.local htmlListFuncs = for funcName, listType in pairs(htmlListFuncs) do p[funcName] = makeWrapper('html_list', listType)end

-- Process arguments for other functions.local otherFuncs = for _, funcName in ipairs(otherFuncs) do p[funcName] = makeWrapper(funcName)end

return p