Jump to content

Module:Random/sandbox

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Mr. Stradivarius (talk | contribs) at 17:08, 25 November 2013 (add idea for a random number generator for big numbers, although it is currently giving errors much of the time). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
-- This module contains a number of functions that make use of random numbers.

local p = {}

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

-- Set the seed for the random number to the current number of edits made to Wikipedia.
-- The English Wikipedia gets dozens of edits each minute, so this is as close to a random seed
-- as we have. On smaller wikis this will produce the same number if the edit count has not changed,
-- so please use it with caution.
math.randomseed(mw.site.stats.edits)

--------------------------------------------------------------------------------------
-- Error helper function
--------------------------------------------------------------------------------------

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('<b class="error">[[Module:Random]] error: %s.</b>', msg)
end

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

function p.bigRandom(lim)
	lim = tostring(lim)
	local isTop = true
	local ret = {}
	for digit in mw.ustring.gmatch(lim, '.') do
		digit = tonumber(digit)
		local rdigit
		if isTop then
			rdigit = math.random(0, digit)
			if digit ~= rdigit then
				isTop = false
			end
		else
			rdigit = math.random(0, 9)
		end
		table.insert(ret, tostring(rdigit))
	end
	ret = table.concat(ret)
	return tonumber(ret)
end

function p._number(args)
	-- Returns 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 and first <= second then -- Second number cannot be less than the first, or it causes an error.
			return math.random(first, second)
		else
			return math.random(first)
		end
	else
		return math.random()
	end
end

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


function p._date(args)
	-- Default date is a random date in the current year.
	-- With two timestamps, the date is a random date between the timestamps.
	-- With one timestamp, the date is a random date between the unix epoch and the timestamp.
	-- Formatting can be specified with args.format. 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 t1 and not t2 then
		startTimestamp = '@0' -- the Unix epoch, 1 January 1970
		endTimestamp = t1
	elseif t1 and t2 then
		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 = p._number{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')
	end
end

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

function p.randomizeArray(t)
	-- 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
	for i = #t, 2, -1 do
		local r = math.random(i)
		t[i], t[r] = t[r], t[i]
	end
	return t
end

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 -- Make sure we have no non-string portal names.
			table.insert(ret, k)
		end
	end
	table.sort(ret)
	for i, v in ipairs(ret) do
		ret[i] = t[v]
	end
	return ret
end

local function makeSeparator(sep)
	if sep == 'space' then
		-- Include an easy way to use spaces as separators.
		return ' '
	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 = {'dot', 'pipe', 'comma', 'tpt-languages'}
		for _, mwsep in ipairs(mwseparators) do
			if sep == mwsep then
				return mw.message.new( sep .. '-separator' ):plain()
			end
		end
		return sep
	end
end

local function makeRandomList(args)
	local list = removeBlanks(args)
	list = p.randomizeArray(list)
	return list
end

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

function p._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 p._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

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

function p.html_list(args, listType)
	-- Randomizes a list and turns it into an HTML list. Uses [[Module:List]].
	listType = listType or 'bulleted'
	local listArgs = makeRandomList(args) -- Arguments for [[Module:List]].
	for k, v in pairs(args) do
		if type(k) == 'string' then
			listArgs[k] = v
		end
	end
	return makeList(listType, listArgs)
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[funcName](args, listType)
	end
end

-- Process arguments for HTML list functions.
local htmlListFuncs = {
	bulleted_list           = 'bulleted',
	unbulleted_list         = 'unbulleted',
	horizontal_list         = 'horizontal',
	ordered_list            = 'ordered',
	horizontal_ordered_list = 'horizontal_ordered'
}
for funcName, listType in pairs(htmlListFuncs) do
	p[funcName] = makeWrapper('html_list', listType)
end

-- Process arguments for other functions.
local otherFuncs = {'number', 'date', 'item', 'list', 'text_list'}
for _, funcName in ipairs(otherFuncs) do
	p[funcName] = makeWrapper('_' .. funcName)
end

return p