Module:Sandbox/Brilliand

--[[

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:

--]]

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

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)