private static (List <(int candidate, ulong?preferred)> Candidates, ulong Bogeyman) GetApprovedCandidates(CoalitionBeatMatrix beatMatrix, RankedBallot ballot) { var candidates = new List <(int candidate, ulong?preferred)>(); var ranking = ballot.Ranking; // Approve of the each candidate `c` which is a first choice. foreach (var c in ranking[0]) { candidates.Add((c, null)); } // With fewer than three ranks, no bogeymen can chance one's preferences. if (ranking.Count < 3) { return(candidates, 0ul); } // Then, for each candidate `b` (the "bogeyman") ranked third or below, // compute the smallest "coalition" of candidates `c` which can beat each potential "bogeyman" `b`, // or includes all candidates one likes better than `b`, whichever comes first. // Approve of each member of the smallest coalition to beat all bogeymen. var coalition = GetCoalition(ranking[0]); var potentialBogeymen = ranking .Skip(2) .SelectMany(b => b) .Where(b => beatMatrix.Beats(coalition, b) != true) .ToList(); if (!potentialBogeymen.Any()) { return(candidates, 0ul); } foreach (var tier in ranking.Skip(1).Take(ranking.Count - 2)) { var greaterBogeymen = potentialBogeymen.Where(b => !tier.Contains(b)).ToList(); // If this tier contains all the bogeymen, the previous tier is where our approval stops. if (!greaterBogeymen.Any()) { break; } // Otherwise, enlist the help of the lesser bogeymen to beat the greater bogeymen. foreach (var c in tier) { candidates.Add((c, coalition)); } coalition |= GetCoalition(tier); var remainingBogeymen = greaterBogeymen .Where(b => beatMatrix.Beats(coalition, b)) .ToList(); // Huzzah! We have beaten all the bogeymen! if (!remainingBogeymen.Any()) { break; } // Otherwise, check the next teir for potential coalition-mates. potentialBogeymen = remainingBogeymen; } return(candidates, GetCoalition(potentialBogeymen)); }
public override ElectionResults GetElectionResults(CandidateComparerCollection <RankedBallot> ballots) { var approvalCount = new int[ballots.CandidateCount]; var firstChoices = new int[ballots.CandidateCount]; var compromises = new CountedList <(int Compromise, ulong Preferred, ulong Bogeymen)>(); // First, compute "coalition" beat matrix var beatMatrix = new CoalitionBeatMatrix(ballots); // Then, use it to determine which candidates each ballot approves of. foreach (var(ballot, count) in ballots.Comparers) { var(approvedCandidates, bogeymen) = GetApprovedCandidates(beatMatrix, ballot); foreach (var(candidate, preferred) in approvedCandidates) { approvalCount[candidate] += count; if (preferred.HasValue) { compromises.Add((candidate, preferred.Value, bogeymen), count); } else { firstChoices[candidate] += count; } } } var results = new ElectionResults(approvalCount.IndexRanking()); results.AddHeading("Votes"); results.AddTable( approvalCount.IndexOrderByDescending() .Select(c => new ElectionResults.Value[] { (ElectionResults.Candidate)c, approvalCount[c], firstChoices[c], approvalCount[c] - firstChoices[c], }), "Total", "First", "Comp."); results.AddHeading("Compromises"); results.AddTable( compromises.Select(c => new ElectionResults.Value[] { (ElectionResults.Candidate)c.Item.Compromise, c.Item.Preferred, c.Item.Bogeymen, c.Count }), "Comp.", "Pref.", "Bogey", "Count" ); beatMatrix.AddDetails(results); return(results); }