Jump to content

Module:Sandbox/Brilliand

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Brilliand (talk | contribs) at 04:59, 26 June 2024. The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
--[[

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

]]

local yesno, 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
		table.insert(t, str:gsub("^%s+", ""):gsub("%s+$", ""))
	end
	return t
end

local function parseRankedVotes(...)
	-- Makes an array of arguments from a list of arguments that might include nils.
	local args = {...} -- Table of arguments. It might contain nils, so we can't use ipairs.
	local ret = {}
	for k, v in pairs(args) do
		v = split_trim(v, ":")
		if(v[1] == nil) then
			v[1] = 1
		end
		if type(v[1]) == "number" then
			table.insert(ret, {split_trim(v[0], ">"), v[1]})
		end
	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)
	return p._IRV(parseRankedVotes(args))
end

function p._IRV(votes)
	local ret = {}
	local totalBallotCount = 0
	for k, v in pairs(votes) do totalBallotCount = totalBallotCount + v[1] end
	while(true) do
		local roundVotes = {}
		local notExhaustedBallots = 0
		for k, v in pairs(votes) do
			local voterCount = v[1]
			local votedFor = v[0][0]
			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 table.insert(roundVotesSorted, {k, v}) end
		table.sort(roundVotesSorted, function(a, b)
			return a[1] < b[1]
		end)
		local weakestCandidate = roundVotesSorted[0]
		local strongestCandidate = roundVotesSorted[table.maxn(roundVotesSorted)]
		if(strongestCandidate[1] > 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[0]) do
					if(not v2 == weakestCandidate[0]) then
						table.insert(cleanedVotes, v2)
					end
				end
				votes[k][0] = cleanedVotes
			end
		end
	end
	table.sort(ret, function(a, b)
		return a[1] > b[1]
	end)
	for k, v in pairs(ret) do
		v[2] = v[1] / totalBallotCount
	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)