Module:Sandbox/Brilliand
Appearance
--[[
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 = {}
error(args)
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)