public static ElectionResults GetElectionResults <T>(CandidateComparerCollection <T> ballots) where T : ScoreBallot { var totals = new int[ballots.CandidateCount]; foreach (var(ballot, count) in ballots.Comparers) { for (int i = 0; i < totals.Length; i++) { totals[i] += ballot.m_scoresByCandidate[i] * count; } } var scores = totals .Select((s, i) => (Candidate: i, Score: s)) .OrderByDescending(a => a.Score) .ToList(); var ranking = scores .GroupBy(a => a.Score, a => a.Candidate) .Select(gp => gp.ToList()) .ToList(); var results = new ElectionResults(ranking); results.AddHeading("Scores"); results.AddCandidateTable(scores); return(results); }
public override ElectionResults GetElectionResults(CandidateComparerCollection <BucketBallot <Bucket> > ballots) { var approvalCount = new int[ballots.CandidateCount]; var firstChoices = new int[ballots.CandidateCount]; var history = new List <(int[] NewApprovalCount, List <int> Winners, CountedList <(ulong Preferred, int Candidate)> Compromises)>(); // Approve of one's first choices. foreach (var(ballot, count) in ballots.Comparers) { foreach (var c in ballot.Buckets.IndexesWhere(a => a == Bucket.Best)) { approvalCount[c] += count; firstChoices[c] += count; } } var winningScore = approvalCount.Max(); var previousWinners = approvalCount.IndexesWhere(a => a == winningScore).ToList(); history.Add((firstChoices, previousWinners, new CountedList <(ulong, int)>())); var potentialApprovalByBallot = ballots.Comparers.ToDictionary( b => b.Item, b => GetCoalition(b.Item.Buckets.IndexesWhere(b => b == Bucket.Good)) ); while (true) { // Test each candidate's "saviour" `s` potential versus each of the previous winners' bogeyman `b`. // Each voter who prefers the saviour `s` over any of the bogeymen `b` will approve of the saviour. // Each voter who prefers any bogeyman `b` over the saviour `s` will approve of the bogeyman. // If no saviour changes the winner, each ballot approves of every candidate they prefer to the current winner and the election ends. // If there exists a saviour who changes the winner, choose the saviour(s) which require the **fewest** voters to change their vote. // Lock in the approvals for that saviour, compute a new winner, and repeat. var previousWinnerCoalition = GetCoalition(previousWinners); var potentialSaviours = Enumerable.Range(0, ballots.CandidateCount).Select(s => { var newApprovalCount = new int[ballots.CandidateCount]; var votersRequired = 0; var approvalsRequired = new List <(BucketBallot <Bucket> Ballot, int Candidate)>(); foreach (var(ballot, count) in ballots.Comparers) { if ((potentialApprovalByBallot[ballot] & GetCoalition(s)) > 0ul && previousWinners.Any(b => ballot.Buckets[b] == Bucket.Bad)) { newApprovalCount[s] += count; approvalsRequired.Add((ballot, s)); votersRequired += count; } if (ballot.Buckets[s] == Bucket.Bad) { foreach (var b in previousWinners.Where(b => (potentialApprovalByBallot[ballot] & GetCoalition(b)) > 0ul)) { newApprovalCount[b] += count; approvalsRequired.Add((ballot, b)); votersRequired += count; } } } var potentialApprovalCount = ballots.CandidateCount.LengthArray(i => approvalCount[i] + newApprovalCount[i]); var potentialWinningScore = potentialApprovalCount.Max(); var potentialWinnerCoalition = GetCoalition(potentialApprovalCount.IndexesWhere(a => a == potentialWinningScore).ToList()); return(Saviour: s, Ballots: approvalsRequired, votersRequired, potentialWinnerCoalition); }) .Where(a => a.potentialWinnerCoalition != previousWinnerCoalition) .ToList(); var newApprovalCount = new int[ballots.CandidateCount]; var compromises = new CountedList <(ulong, int)>(); if (!potentialSaviours.Any()) { // No-one can beat the current winner, so just support as much as possible. foreach (var(ballot, count) in ballots.Comparers) { // No compromise necessary if we rank the winner first or second. if (!previousWinners.Any(b => ballot.Buckets[b] == Bucket.Bad)) { continue; } var bestCoalition = GetCoalition(ballot.Buckets.IndexesWhere(b => b == Bucket.Best)); foreach (var candidate in GetCandidates(potentialApprovalByBallot[ballot])) { approvalCount[candidate] += count; newApprovalCount[candidate] += count; compromises.Add((bestCoalition, candidate), count); } } // Make sure this doesn't output a scoring that doesn't preserve the winner :) foreach (var s in approvalCount.IndexesWhere((a, s) => a >= winningScore && !previousWinners.Contains(s))) { foreach (var(ballot, count) in ballots.Comparers) { if (ballot.Buckets[s] == Bucket.Bad) { var bestCoalition = GetCoalition(ballot.Buckets.IndexesWhere(b => b == Bucket.Best)); foreach (var b in previousWinners.Where(b => (potentialApprovalByBallot[ballot] & GetCoalition(b)) > 0ul)) { approvalCount[b] += count; newApprovalCount[b] += count; compromises.Add((bestCoalition, b), count); } } } } } else { // Choosing the maximum `votersRequired` makes this method less satisfactory and more suceptible to tactical voting. // Approving of all candidates we like better than the saviour *at this step* makes us more suceptible to tactical voting without affecting satisfaction. var minimalVotersRequired = potentialSaviours.Select(a => a.votersRequired).Min(); foreach (var(ballot, newApprovals) in potentialSaviours .Where(a => a.votersRequired == minimalVotersRequired) .SelectMany(a => a.Ballots) .GroupBy(a => a.Ballot, a => a.Candidate) .Select(gp => (gp.Key, gp.ToList()))) { ballots.Comparers.TryGetCount(ballot, out var count); var bestCoalition = GetCoalition(ballot.Buckets.IndexesWhere(b => b == Bucket.Best)); foreach (var candidate in newApprovals) { approvalCount[candidate] += count; newApprovalCount[candidate] += count; compromises.Add((bestCoalition, candidate), count); } potentialApprovalByBallot[ballot] ^= GetCoalition(newApprovals); } } winningScore = approvalCount.Max(); previousWinners = approvalCount.IndexesWhere(a => a == winningScore).ToList(); if (newApprovalCount.Any(c => c > 0)) { history.Add((newApprovalCount, previousWinners, compromises)); } if (!potentialSaviours.Any()) { break; } } var results = new ElectionResults(approvalCount.IndexRanking()); if (history.Count == 1) { results.AddHeading("Approval"); results.AddCandidateTable(approvalCount); } else { results.AddHeading("Rounds"); results.AddTable(history.Select((h, i) => new ElectionResults.Value[] { i + 1, h.Winners, }.Concat(h.NewApprovalCount.Select(a => (ElectionResults.Value)a)) .ToArray()) .Append(approvalCount.Select(a => (ElectionResults.Value)a).Prepend(results.Ranking[0]).Prepend("Total").ToArray()), Enumerable.Range(0, ballots.CandidateCount).Select(c => (ElectionResults.Candidate)c).Prepend <ElectionResults.Value>("Winner").ToArray()); var roundNumber = 1; foreach (var round in history.Skip(1)) { roundNumber++; results.AddHeading("Round " + roundNumber + " Compromises"); results.AddTable(round.Compromises.Select(a => new ElectionResults.Value[] { (ElectionResults.Candidate)a.Item.Candidate, a.Item.Preferred, a.Count }), "Comp.", "Pref.", "Count"); } } return(results); }
public override ElectionResults GetElectionResults(CandidateComparerCollection <RankedBallot> ballots) { var approvalCount = new int[ballots.CandidateCount]; var firstChoices = new int[ballots.CandidateCount]; var approvalByBallot = new Dictionary <RankedBallot, ulong>(); var history = new List <(int[] NewApprovalCount, List <int> Winners, CountedList <(ulong Preferred, int Candidate)> Compromises)>(); foreach (var(ballot, count) in ballots.Comparers) { var firstChoiceCandidates = ballot.RanksByCandidate.IndexesWhere(a => a == 0).ToList(); approvalByBallot[ballot] = GetCoalition(firstChoiceCandidates); // Approve of one's first choices. foreach (var c in firstChoiceCandidates) { approvalCount[c] += count; firstChoices[c] += count; } } var winningScore = approvalCount.Max(); var winners = approvalCount.IndexesWhere(a => a == winningScore).ToList(); history.Add((firstChoices, winners, new CountedList <(ulong, int)>())); var beatMatrix = ballots.GetBeatMatrix(); var previousWinners = winners.ToDictionary( b => b, b => Enumerable.Range(0, ballots.CandidateCount) .Where(s => beatMatrix.Beats(s, b)) .ToList()); while (true) { var newApprovalCount = new int[ballots.CandidateCount]; var compromises = new CountedList <(ulong, int)>(); foreach (var(ballot, count) in ballots.Comparers) { var approveUntilRank = 0; var approvalCoalition = 0ul; // If one approves of all candidates which beat one of the previous winners, do so (and approve of candidates one likes better than the worst of those) // (if we don't, neither will the people who prefer the other saviours -- and we won't be able to beat the bogeyman) // otherwise, approve of all candidates one likes better than said winner. foreach (var(bogeyman, saviours) in previousWinners) { var bogeymanRank = ballot.RanksByCandidate[bogeyman]; var saviourRanks = saviours.Select(s => ballot.RanksByCandidate[s]).ToList(); if (saviours.Any() && saviourRanks.All(sr => sr > bogeymanRank)) { approvalCoalition |= GetCoalition(saviours); approveUntilRank = saviourRanks.Append(approveUntilRank).Min(); } else { approveUntilRank = Math.Min(approveUntilRank, bogeymanRank); } } var approvedCoalition = approvalByBallot[ballot]; var newApprovals = ballot.RanksByCandidate .IndexesWhere((rank, candidate) => (rank > approveUntilRank || (approvalCoalition & GetCoalition(candidate)) > 0) && (approvedCoalition & GetCoalition(candidate)) == 0ul) .ToList(); foreach (var candidate in newApprovals) { approvalCount[candidate] += count; newApprovalCount[candidate] += count; compromises.Add((approvedCoalition, candidate), count); } if (newApprovals.Any()) { approvalByBallot[ballot] = approvedCoalition | GetCoalition(newApprovals); } } winningScore = approvalCount.Max(); winners = approvalCount.IndexesWhere(a => a == winningScore).ToList(); if (newApprovalCount.Any(c => c > 0)) { history.Add((newApprovalCount, winners, compromises)); } var newBogeymen = winners.Where(w => !previousWinners.ContainsKey(w)); if (!newBogeymen.Any()) { break; } foreach (var b in newBogeymen) { previousWinners[b] = Enumerable.Range(0, ballots.CandidateCount) .Where(s => beatMatrix.Beats(s, b)) .ToList(); } } var results = new ElectionResults(approvalCount.IndexRanking()); if (history.Count == 1) { results.AddHeading("Approval"); results.AddCandidateTable(approvalCount); } else { results.AddHeading("Rounds"); results.AddTable(history.Select((h, i) => new ElectionResults.Value[] { i + 1, h.Winners, }.Concat(h.NewApprovalCount.Select(a => (ElectionResults.Value)a)) .ToArray()) .Append(approvalCount.Select(a => (ElectionResults.Value)a).Prepend(results.Ranking[0]).Prepend("Total").ToArray()), Enumerable.Range(0, ballots.CandidateCount).Select(c => (ElectionResults.Candidate)c).Prepend <ElectionResults.Value>("Winner").ToArray()); results.AddHeading("Winners"); results.AddTable( previousWinners.Select(kvp => new ElectionResults.Value[] { (ElectionResults.Candidate)kvp.Key, kvp.Value }), "Beaten By"); var roundNumber = 1; foreach (var round in history.Skip(1)) { roundNumber++; results.AddHeading("Round " + roundNumber + " Compromises"); results.AddTable(round.Compromises.Select(a => new ElectionResults.Value[] { (ElectionResults.Candidate)a.Item.Candidate, a.Item.Preferred, a.Count }), "Comp.", "Pref.", "Count"); } } return(results); }
public override ElectionResults GetElectionResults(CandidateComparerCollection <RankedBallot> ballots) { var approvalCount = new int[ballots.CandidateCount]; var firstChoices = new int[ballots.CandidateCount]; var approvalByBallot = new Dictionary <RankedBallot, ulong>(); var history = new List <(int[] NewApprovalCount, List <int> Winners, CountedList <(ulong Preferred, int Candidate)> Compromises)>(); foreach (var(ballot, count) in ballots.Comparers) { var firstChoiceCandidates = ballot.RanksByCandidate.IndexesWhere(a => a == 0).ToList(); approvalByBallot[ballot] = GetCoalition(firstChoiceCandidates); // Approve of one's first choices. foreach (var c in firstChoiceCandidates) { approvalCount[c] += count; firstChoices[c] += count; } } var winningScore = approvalCount.Max(); var previousWinners = approvalCount.IndexesWhere(a => a == winningScore).ToList(); history.Add((firstChoices, previousWinners, new CountedList <(ulong, int)>())); while (true) { // Test each candidate's "saviour" `s` potential versus each of the previous winners' bogeyman `b`. // Each voter who prefers the saviour `s` over any of the bogeymen `b` will approve of the saviour (and each other candidate one prefers to the saviour). // If no saviour changes the winner, each ballot approves of every candidate they prefer to the current winner and the election ends. // If there exists a saviour who changes the winner, choose the saviour(s) which require the **fewest** voters to change their vote. // Lock in the approvals for that saviour, compute a new winner, and repeat. var previousWinnerCoalition = GetCoalition(previousWinners); var potentialSaviours = Enumerable.Range(0, ballots.CandidateCount).Select(s => { var newApprovalCount = new int[ballots.CandidateCount]; var votersRequired = 0; var approvalsRequired = new List <RankedBallot>(); foreach (var(ballot, count) in ballots.Comparers) { var saviourRank = ballot.RanksByCandidate[s]; var lowestBogeymanRank = previousWinners .Select(b => ballot.RanksByCandidate[b]) .Min(); if (lowestBogeymanRank < saviourRank && (approvalByBallot[ballot] & GetCoalition(s)) == 0ul) { newApprovalCount[s] += count; approvalsRequired.Add(ballot); votersRequired += count; } } var potentialApprovalCount = ballots.CandidateCount.LengthArray(i => approvalCount[i] + newApprovalCount[i]); var potentialWinningScore = potentialApprovalCount.Max(); var potentialWinnerCoalition = GetCoalition(potentialApprovalCount.IndexesWhere(a => a == potentialWinningScore).ToList()); return(Saviour: s, Ballots: approvalsRequired, votersRequired, potentialWinnerCoalition); }) .Where(a => a.potentialWinnerCoalition != previousWinnerCoalition) .ToList(); var newApprovalCount = new int[ballots.CandidateCount]; var compromises = new CountedList <(ulong, int)>(); if (!potentialSaviours.Any()) { // No-one can beat the current winner, so just support as much as possible. foreach (var(ballot, count) in ballots.Comparers) { var lowestWinnerRank = previousWinners .Select(b => ballot.RanksByCandidate[b]) .Min(); // No compromise necessary if we rank the winner first or second. if (lowestWinnerRank >= -1) { continue; } var approvedCoalition = approvalByBallot[ballot]; // Approve of each candidate `c` that one likes better than the worst winner // which one has not already approved of var newApprovals = ballot.RanksByCandidate .IndexesWhere((rank, candidate) => (rank > lowestWinnerRank) && (approvedCoalition & GetCoalition(candidate)) == 0ul) .ToList(); foreach (var candidate in newApprovals) { approvalCount[candidate] += count; newApprovalCount[candidate] += count; compromises.Add((approvedCoalition, candidate), count); } } } else { // Choosing the maximum `votersRequired` makes this method less satisfactory and more suceptible to tactical voting. // Approving of all candidates we like better than the saviour *at this step* makes us more suceptible to tactical voting without affecting satisfaction. var minimalVotersRequired = potentialSaviours.Select(a => a.votersRequired).Min(); foreach (var(ballot, newApprovals) in potentialSaviours .Where(a => a.votersRequired == minimalVotersRequired) .SelectMany(a => a.Ballots.Select(b => (Ballot: b, Saviour: a.Saviour))) .GroupBy(a => a.Ballot, a => a.Saviour) .Select(gp => (gp.Key, gp.ToList()))) { ballots.Comparers.TryGetCount(ballot, out var count); var preferredCoalition = approvalByBallot[ballot]; foreach (var candidate in newApprovals) { approvalCount[candidate] += count; newApprovalCount[candidate] += count; compromises.Add((preferredCoalition, candidate), count); } approvalByBallot[ballot] = preferredCoalition | GetCoalition(newApprovals); } } winningScore = approvalCount.Max(); previousWinners = approvalCount.IndexesWhere(a => a == winningScore).ToList(); if (newApprovalCount.Any(c => c > 0)) { history.Add((newApprovalCount, previousWinners, compromises)); } if (!potentialSaviours.Any()) { break; } } var results = new ElectionResults(approvalCount.IndexRanking()); if (history.Count == 1) { results.AddHeading("Approval"); results.AddCandidateTable(approvalCount); } else { results.AddHeading("Rounds"); results.AddTable(history.Select((h, i) => new ElectionResults.Value[] { i + 1, h.Winners, }.Concat(h.NewApprovalCount.Select(a => (ElectionResults.Value)a)) .ToArray()) .Append(approvalCount.Select(a => (ElectionResults.Value)a).Prepend(results.Ranking[0]).Prepend("Total").ToArray()), Enumerable.Range(0, ballots.CandidateCount).Select(c => (ElectionResults.Candidate)c).Prepend <ElectionResults.Value>("Winner").ToArray()); var roundNumber = 1; foreach (var round in history.Skip(1)) { roundNumber++; results.AddHeading("Round " + roundNumber + " Compromises"); results.AddTable(round.Compromises.Select(a => new ElectionResults.Value[] { (ElectionResults.Candidate)a.Item.Candidate, a.Item.Preferred, a.Count }), "Comp.", "Pref.", "Count"); } } return(results); }
public override ElectionResults GetElectionResults(CandidateComparerCollection <RankedBallot> ballots) { var approvalCount = new int[ballots.CandidateCount]; var firstChoices = new int[ballots.CandidateCount]; var approvalByBallot = new Dictionary <RankedBallot, ulong>(); var history = new List <(int[] NewApprovalCount, List <int> Winners, CountedList <(ulong Preferred, int Candidate)> Compromises)>(); foreach (var(ballot, count) in ballots.Comparers) { var firstChoiceCandidates = ballot.RanksByCandidate.IndexesWhere(a => a == 0).ToList(); approvalByBallot[ballot] = GetCoalition(firstChoiceCandidates); // Approve of one's first choices. foreach (var c in firstChoiceCandidates) { approvalCount[c] += count; firstChoices[c] += count; } } var winningScore = approvalCount.Max(); var winners = approvalCount.IndexesWhere(a => a == winningScore).ToList(); history.Add((firstChoices, winners, new CountedList <(ulong, int)>())); var previousWinners = winners.ToHashSet(); while (true) { var newApprovalCount = new int[ballots.CandidateCount]; var compromises = new CountedList <(ulong, int)>(); foreach (var(ballot, count) in ballots.Comparers) { var lowestWinnerRank = previousWinners .Select(w => ballot.RanksByCandidate[w]) .Min(); // If all winners are ranked one or two, no compromise is necessary. if (lowestWinnerRank == 0 || lowestWinnerRank == -1) { continue; } var approvedCoalition = approvalByBallot[ballot]; // Approve of the each candidate `c` that one likes better than any of the previous winners // which one has not already approved of var newApprovals = ballot.RanksByCandidate .IndexesWhere((rank, candidate) => rank > lowestWinnerRank && (approvedCoalition & GetCoalition(candidate)) == 0ul) .ToList(); foreach (var candidate in newApprovals) { approvalCount[candidate] += count; newApprovalCount[candidate] += count; compromises.Add((approvedCoalition, candidate), count); } if (newApprovals.Any()) { approvalByBallot[ballot] = approvedCoalition | GetCoalition(newApprovals); } } winningScore = approvalCount.Max(); winners = approvalCount.IndexesWhere(a => a == winningScore).ToList(); if (newApprovalCount.Any(c => c > 0)) { history.Add((newApprovalCount, winners, compromises)); } var newWinners = winners.Where(w => !previousWinners.Contains(w)); if (!newWinners.Any()) { break; } foreach (var w in newWinners) { previousWinners.Add(w); } } var results = new ElectionResults(approvalCount.IndexRanking()); if (history.Count == 1) { results.AddHeading("Approval"); results.AddCandidateTable(approvalCount); } else { results.AddHeading("Rounds"); results.AddTable(history.Select((h, i) => new ElectionResults.Value[] { i + 1, h.Winners, }.Concat(h.NewApprovalCount.Select(a => (ElectionResults.Value)a)) .ToArray()) .Append(approvalCount.Select(a => (ElectionResults.Value)a).Prepend(results.Ranking[0]).Prepend("Total").ToArray()), Enumerable.Range(0, ballots.CandidateCount).Select(c => (ElectionResults.Candidate)c).Prepend <ElectionResults.Value>("Winner").ToArray()); var roundNumber = 1; foreach (var round in history.Skip(1)) { roundNumber++; results.AddHeading("Round " + roundNumber + " Compromises"); results.AddTable(round.Compromises.Select(a => new ElectionResults.Value[] { (ElectionResults.Candidate)a.Item.Candidate, a.Item.Preferred, a.Count }), "Comp.", "Pref.", "Count"); } } return(results); }
public override ElectionResults GetElectionResults(CandidateComparerCollection <BucketBallot <Bucket> > ballots) { var approvalCount = new int[ballots.CandidateCount]; var firstChoices = new int[ballots.CandidateCount]; var compromiseBallots = new HashSet <BucketBallot <Bucket> >(); var history = new List <(int[] NewApprovalCount, List <int> Winners, CountedList <(ulong Preferred, int Candidate)> Compromises)>(); // Approve of one's first choices. foreach (var(ballot, count) in ballots.Comparers) { foreach (var c in ballot.Buckets.IndexesWhere(a => a == Bucket.Best)) { approvalCount[c] += count; firstChoices[c] += count; } } var winningScore = approvalCount.Max(); var winners = approvalCount.IndexesWhere(a => a == winningScore).ToList(); history.Add((firstChoices, winners, new CountedList <(ulong, int)>())); var previousWinners = winners.ToHashSet(); while (true) { var newApprovalCount = new int[ballots.CandidateCount]; var compromises = new CountedList <(ulong, int)>(); foreach (var(ballot, count) in ballots.Comparers) { // Can only compromise once if (compromiseBallots.Contains(ballot)) { continue; } // Compromise is only necessary if a winner is bad. if (previousWinners.All(w => ballot.Buckets[w] != Bucket.Bad)) { continue; } compromiseBallots.Add(ballot); var bestCoalition = GetCoalition(ballot.Buckets.IndexesWhere(a => a == Bucket.Best)); foreach (var candidate in ballot.Buckets.IndexesWhere(a => a == Bucket.Good)) { approvalCount[candidate] += count; newApprovalCount[candidate] += count; compromises.Add((bestCoalition, candidate), count); } } winningScore = approvalCount.Max(); winners = approvalCount.IndexesWhere(a => a == winningScore).ToList(); if (newApprovalCount.Any(c => c > 0)) { history.Add((newApprovalCount, winners, compromises)); } var newWinners = winners.Where(w => !previousWinners.Contains(w)); if (!newWinners.Any()) { break; } foreach (var w in newWinners) { previousWinners.Add(w); } } var results = new ElectionResults(approvalCount.IndexRanking()); if (history.Count == 1) { results.AddHeading("Approval"); results.AddCandidateTable(approvalCount); } else { results.AddHeading("Rounds"); results.AddTable(history.Select((h, i) => new ElectionResults.Value[] { i + 1, h.Winners, }.Concat(h.NewApprovalCount.Select(a => (ElectionResults.Value)a)) .ToArray()) .Append(approvalCount.Select(a => (ElectionResults.Value)a).Prepend(results.Ranking[0]).Prepend("Total").ToArray()), Enumerable.Range(0, ballots.CandidateCount).Select(c => (ElectionResults.Candidate)c).Prepend <ElectionResults.Value>("Winner").ToArray()); var roundNumber = 1; foreach (var round in history.Skip(1)) { roundNumber++; results.AddHeading("Round " + roundNumber + " Compromises"); results.AddTable(round.Compromises.Select(a => new ElectionResults.Value[] { (ElectionResults.Candidate)a.Item.Candidate, a.Item.Preferred, a.Count }), "Comp.", "Pref.", "Count"); } } return(results); }