Jump to content

Module:Sandbox/Brilliand

From Wikipedia, the free encyclopedia
--[[

This module provides vote-counting functions for various voting systems.

]]

local getArgs -- lazily initialized

local p = {} -- Holds functions to be returned from #invoke, and functions to make available to other Lua modules.
local wrap = {} -- Holds wrapper functions that process arguments from #invoke. These act as intemediary between functions meant for #invoke and functions meant for Lua.

--[[
Helper functions used to avoid redundant code.
]]

local function split_trim(s, sep)
	if sep == nil then
		sep = "%s"
	end
	local t = {}
	for str in string.gmatch(s, "([^"..sep.."]+)") do
		t[#t+1] = str:gsub("^%s+", ""):gsub("%s+$", "")
	end
	return t
end

local function parseRankedVotes(args)
	-- Makes an array of arguments from a list of arguments that might include nils.
	local ret = {}
	for k, v in pairs(args) do
		v = split_trim(v, ":")
		ret[#ret+1] = {split_trim(v[1], ">"), tonumber(v[2]) or 1}
	end
	return ret
end

--[[
IRV

Processes a set of votes using the rules of Instant-Runoff Voting

Usage:
{{#invoke:Math | IRV | vote 1 : voter count | vote 2 : voter count | vote 3 : voter count | ... }}
--]]

function wrap.IRV(args)
	local output = p._IRV(parseRankedVotes(args))
	local html = mw.html.create('table')
	for k, v in pairs(output) do
		local row = html:tag('tr')
		for k2, v2 in pairs(v) do
			local cell = html:tag('td')
				:wikitext(v2)
		end
	end
	return tostring(html)
end

function p._IRV(votes)
	local ret = {}
	local totalBallotCount = 0
	for k, v in pairs(votes) do totalBallotCount = totalBallotCount + v[2] end
	while(true) do
		local roundVotes = {}
		local notExhaustedBallots = 0
		for k, v in pairs(votes) do
			local voterCount = v[2]
			local votedFor = v[1][1]
			if(votedFor) then
				if(roundVotes[votedFor] == nil) then
					roundVotes[votedFor] = voterCount
				else
					roundVotes[votedFor] = roundVotes[votedFor] + voterCount
				end
				notExhaustedBallots = notExhaustedBallots + voterCount
			end
		end
		local roundVotesSorted = {}
		for k, v in pairs(roundVotes) do roundVotesSorted[#roundVotesSorted+1] = {k, v} end
		table.sort(roundVotesSorted, function(a, b)
			return a[2] < b[2]
		end)
		local weakestCandidate = roundVotesSorted[1]
		local strongestCandidate = roundVotesSorted[#roundVotesSorted]
		if(strongestCandidate[2] > notExhaustedBallots/2) then
			for k, v in pairs(roundVotesSorted) do
				table.insert(ret, v)
			end
			break
		else
			table.insert(ret, weakestCandidate)
			-- Erase the weakest candidate from all votes
			for k, v in pairs(votes) do
				local cleanedVotes = {}
				for k2, v2 in pairs(v[1]) do
					if(not (v2 == weakestCandidate[1])) then
						table.insert(cleanedVotes, v2)
					end
				end
				votes[k][1] = cleanedVotes
			end
		end
	end
	table.sort(ret, function(a, b)
		return a[2] > b[2]
	end)
	for k, v in pairs(ret) do
		v[3] = string.format("%.1f%%", v[2] / totalBallotCount * 100)
	end
	return ret
end

--[[
Wrapper function that does basic argument processing. This ensures that all functions from #invoke can use either the current
frame or the parent frame, and it also trims whitespace for all arguments and removes blank arguments.
]]

local mt = { __index = function(t, k)
	return function(frame)
		if not getArgs then
			getArgs = require('Module:Arguments').getArgs
		end
		return wrap[k](getArgs(frame))  -- Argument processing is left to Module:Arguments. Whitespace is trimmed and blank arguments are removed.
	end
end }

return setmetatable(p, mt)