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); }
void switchResults_Click(object sender, EventArgs e) { electionResults = electionResults == ElectionResults.e2012 ? ElectionResults.e2016 : ElectionResults.e2012; switchResults.Text = electionResults == ElectionResults.e2016 ? "Show 2012" : "Show 2016"; LoadElectionData(); tsElectionSimilutor.IsOn = electionData.IsPlayGround; }
public void AddDetails(ElectionResults results) { results.AddHeading("Beat Matrix"); results.AddTable(m_beatMatrix.SelectMany((d, i) => d.Select(kvp => new ElectionResults.Value[] { (ElectionResults.Candidate)i, kvp.Key, kvp.Value, })), "Cand.", "Coa.", "Support" ); }
public async Task <ElectionResults> GetResults(Election election) { var request = _service.Spreadsheets.Values.BatchGet(_spreadsheetId); var ranges = new List <string>(); foreach (var race in election.Races) { var range = $"{StarSymbol}{race.Caption}!A:{ToColumnName(race.Candidates.Count+1)}"; ranges.Add(range); } request.Ranges = new Repeatable <string>(ranges); var response = await request.ExecuteAsync().ConfigureAwait(false); var valueRanges = response.ValueRanges.ToArray(); var results = new ElectionResults { Title = election.Title, Races = new List <RaceResults>() }; for (var raceIndex = 0; raceIndex < election.Races.Count; raceIndex++) { var race = election.Races[raceIndex]; var valueRange = valueRanges[raceIndex]; var raceResults = new RaceResults { Title = race.Caption, Candidates = race.Candidates.ToArray(), Votes = new Vote[valueRange.Values.Count - 1] }; var index = 0; foreach (var range in valueRange.Values.ToArray().Slice(1)) { var array = range.ToArray(); var scores = new int[array.Length - 1]; for (var columnIndex = 1; columnIndex < array.Length; columnIndex++) { var score = int.Parse(array[columnIndex].ToString()); scores[columnIndex - 1] = score; } var vote = new Vote { VoterId = array[0].ToString(), Scores = scores.ToArray() }; raceResults.Votes[index++] = vote; } results.Races.Add(raceResults); } return(results); }
public Map() { InitializeComponent(); this.mapControl.EnableScrolling = EnableMapScrollingInStateView; this.usStateInfo = new LoadedMapInfo() { Layer = this.stateLayer }; this.usCountyInfo = new LoadedMapInfo(); LoadUSCountyShapes(); this.electionResults = ElectionResults.e2012; LoadElectionData(); CreateColorLegend(); switchResults.Visible = true; }
public override ElectionResults GetElectionResults(CandidateComparerCollection <BucketBallot <Bucket> > ballots) { var firstChoices = new int[ballots.CandidateCount]; var maximumApprovalCount = new int[ballots.CandidateCount]; foreach (var(ballot, count) in ballots.Comparers) { foreach (var c in ballot.Buckets.IndexesWhere(a => a == Bucket.Best)) { firstChoices[c] += count; maximumApprovalCount[c] += count; } foreach (var c in ballot.Buckets.IndexesWhere(a => a == Bucket.Good)) { maximumApprovalCount[c] += count; } } var firstChoiceWinners = firstChoices.MaxIndexes().ToList(); var maximumApprovalWinners = maximumApprovalCount.MaxIndexes().ToList(); var approvalCount = firstChoices.SelectToArray(c => c); var winners = ballots.Compare(firstChoiceWinners[0], maximumApprovalWinners[0]) > 0 ? firstChoiceWinners : maximumApprovalWinners; var results = new ElectionResults(new List <List <int> > { winners }); results.AddHeading("Votes"); results.AddTable( approvalCount.IndexOrderByDescending() .Select(c => new ElectionResults.Value[] { (ElectionResults.Candidate)c, approvalCount[c], firstChoices[c], approvalCount[c] - firstChoices[c], }), "Votes", "First", "Comp."); return(results); }
/// <summary> /// Run an election with a set of randomly generated poeple. Once they are run, /// remove each non-winning candidate in turn and count the number of times /// that the winner changes. /// </summary> /// <returns></returns> public async Task<ElectionResults> RunElection() { // Generate the people. var people = GeneratePeople().ToArray(); // Next, run the election var result = await RunSingleElectionInternal(people); // Now, loop over each candidate and remove them... unless they are the winner! var winner = result[0].candidate; int flips = 0; for (int i_cand = 0; i_cand < NumberOfCandidates; i_cand++) { if (i_cand != winner) { var peopleWithOut = people.Select(p => p.RemoveCandidates(i_cand)).ToArray(); var resultWithOut = await RunSingleElectionInternal(peopleWithOut); if (resultWithOut[0].candidate != winner) flips++; } } // Build the result var r = new ElectionResults(); r.flips = flips; r.candidateOrdering = (from rs in result orderby rs.ranking descending select rs.candidate).ToArray(); return r; }
public override ElectionResults GetElectionResults(CandidateComparerCollection <BucketBallot <Bucket> > ballots) { var beatMatrix = ballots.GetBeatMatrix(); var bogeymen = beatMatrix .GetSchulzeSet() .ToList(); var approvalCount = new int[ballots.CandidateCount]; var firstChoices = new int[ballots.CandidateCount]; var compromises = new CountedList <(ulong Preferred, int Compromise, ulong Bogeymen)>(); foreach (var(ballot, count) in ballots.Comparers) { // Always approve of first choices. foreach (var c in ballot.Buckets.IndexesWhere(b => b == Bucket.Best)) { approvalCount[c] += count; firstChoices[c] += count; } // If one disapproves of any bogeymen, support one's second choices too. var bogeymanCoalition = GetCoalition(bogeymen.Where(b => ballot.Buckets[b] == Bucket.Bad)); if (bogeymanCoalition != 0ul) { var best = GetCoalition(ballot.Buckets.IndexesWhere(b => b == Bucket.Best)); foreach (var c in ballot.Buckets.IndexesWhere(b => b == Bucket.Good)) { approvalCount[c] += count; compromises.Add((best, c, bogeymanCoalition), 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" ); return(results); }
public List <MsgBase> HandleResoultRequest(MsgBase msg) { List <MsgBase> results = new List <MsgBase>(); ElectionResultsRequest resultsRequest = msg as ElectionResultsRequest; if (resultsRequest.State == "") { ElectionResults result = new ElectionResults() { RequestUID = resultsRequest.Base_MsgUID, Election = resultsRequest.ElectionID, State = "" }; Dictionary <string, VotingResult> summaryResults = new Dictionary <string, VotingResult>(); foreach (var state in m_Votes) { foreach (VotesDetails votes in state.Value.Values) { if (summaryResults.ContainsKey(votes.CandidateId)) { summaryResults[votes.CandidateId].VoteCount += votes.VoteCount; summaryResults[votes.CandidateId].ElectoralVotesWon += votes.ElectoralVotesWon; } else { CandidateDetails candidate = m_Candidates[votes.CandidateId]; VotingResult vResult = new VotingResult() { CandidateId = votes.CandidateId, CandidateFirstName = candidate.FirstName, CandidateLastName = candidate.LastName, Party = candidate.Party, ElectoralVotes = 348, ElectoralVotesWon = votes.ElectoralVotesWon, VoteCount = votes.VoteCount, FinalResults = false }; summaryResults.Add(vResult.CandidateId, vResult); } } } int allVotes = summaryResults.Values.Sum(v => v.VoteCount); foreach (VotingResult vote in summaryResults.Values) { vote.VotePercent = (vote.VoteCount / allVotes) * 100; result.Voting.Add(vote); } results.Add(result); } else if (resultsRequest.State == "All") { foreach (var state in m_Votes) { ElectionResults result = new ElectionResults() { RequestUID = resultsRequest.Base_MsgUID, Election = resultsRequest.ElectionID, State = state.Key }; foreach (VotesDetails votes in state.Value.Values) { CandidateDetails candidate = m_Candidates[votes.CandidateId]; result.Voting.Add(new VotingResult() { CandidateId = votes.CandidateId, CandidateFirstName = candidate.FirstName, CandidateLastName = candidate.LastName, Party = candidate.Party, ElectoralVotes = votes.ElectoralVotes, ElectoralVotesWon = votes.ElectoralVotesWon, VoteCount = votes.VoteCount, VotePercent = votes.VotePercent, FinalResults = votes.FinalResults }); } results.Add(result); } } else { if (m_Votes.ContainsKey(resultsRequest.State)) { ElectionResults result = new ElectionResults() { RequestUID = resultsRequest.Base_MsgUID, Election = resultsRequest.ElectionID, State = resultsRequest.State }; foreach (VotesDetails votes in m_Votes[resultsRequest.State].Values) { CandidateDetails candidate = m_Candidates[votes.CandidateId]; result.Voting.Add(new VotingResult() { CandidateId = votes.CandidateId, CandidateFirstName = candidate.FirstName, CandidateLastName = candidate.LastName, Party = candidate.Party, ElectoralVotes = votes.ElectoralVotes, ElectoralVotesWon = votes.ElectoralVotesWon, VoteCount = votes.VoteCount, VotePercent = votes.VotePercent, FinalResults = votes.FinalResults }); } results.Add(result); } else { results.Add(new ElectionResults()); } } 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 <BucketBallot <Bucket> > ballots) { // The [Mutual majority criterion] states that if there is a subset S of the candidates, // such that more than half of the voters strictly prefer every member of S to every candidate outside of S, // this majority voting sincerely, the winner must come from S. // // For each "mutual majority" set, each voter in said majority approves of all members of the majority set. // The winner is the one with the most approvals. var approvalCount = new int[ballots.CandidateCount]; var firstChoices = new int[ballots.CandidateCount]; var approvalByBallot = new Dictionary <RankedBallot, ulong>(); // 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 beatMatrix = ballots.GetBeatMatrix(); var candidates = Enumerable.Range(0, ballots.CandidateCount); var compromises = new CountedList <(ulong Preferred, int Compromise, ulong Bogeymen)>(); foreach (var(ballot, count) in ballots.Comparers) { var best = ballot.Buckets.IndexesWhere(a => a == Bucket.Best).ToList(); var good = ballot.Buckets.IndexesWhere(a => a == Bucket.Good).ToList(); var bad = ballot.Buckets.IndexesWhere(a => a == Bucket.Bad).ToList(); // Support each "good" candidate `g` such that // For each "best" candidate `c` // There exists a "bad" candidate `b` (the 'bogeyman') such that `b` beats `c` in first-choice votes but loses to `g` one-on-one if (good.Any() && bad.Any()) { var bogeymen = bad .Where(b => best.All(c => firstChoices[b] > firstChoices[c])) .ToList(); if (bogeymen.Any()) { var bestCoalition = GetCoalition(best); var bogeymenCoalition = GetCoalition(bogeymen); foreach (var c in good.Where(g => bogeymen.Any(b => beatMatrix.Beats(g, b)))) { approvalCount[c] += count; compromises.Add((bestCoalition, c, bogeymenCoalition), 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"); 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); }
public override ElectionResults GetElectionResults(CandidateComparerCollection <RankedBallot> ballots) { var beatMatrix = ballots.GetBeatMatrix(); var candidates = Enumerable.Range(0, ballots.CandidateCount); var approvalCount = new int[ballots.CandidateCount]; var firstChoices = new int[ballots.CandidateCount]; var compromises = new CountedList <(ulong Preferred, int Compromise, ulong Bogeymen)>(); foreach (var(ballot, count) in ballots.Comparers) { var ranking = ballot.Ranking; // Approve of the each candidate `c` which is a first choice. foreach (var c in ranking[0]) { firstChoices[c] += count; approvalCount[c] += count; } // Approve of the each candidate `c` such that // For each candidate `a` which one prefers to `c`, // There exists a candidate `b` (the 'bogeyman') such that one prefers `c` to `b` and `b` beats `a` one-on-one if (ranking.Count >= 3) { var preferredCandidates = ranking[0].ToList(); var potentialBogeymen = Enumerable.Range(0, ballots.CandidateCount).ToHashSet(); potentialBogeymen.ExceptWith(preferredCandidates); foreach (var tier in ranking.Skip(1).Take(ranking.Count - 2)) { potentialBogeymen.ExceptWith(tier); if (preferredCandidates.All(a => potentialBogeymen.Any(b => beatMatrix.Beats(b, a)))) { var preferredCoalition = GetCoalition(preferredCandidates); var bogeymen = GetCoalition(potentialBogeymen.Where(b => preferredCandidates.Any(a => beatMatrix.Beats(b, a)))); foreach (var c in tier) { approvalCount[c] += count; compromises.Add((preferredCoalition, c, bogeymen), count); } } preferredCandidates.AddRange(tier); } } } 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"); return(results); }
public override ElectionResults GetElectionResults(CandidateComparerCollection <RankedBallot> ballots) { var beatMatrix = ballots.GetBeatMatrix(); var bogeymen = beatMatrix .GetSchulzeSet() .Select(b => ( Bogeyman: b, Saviours: Enumerable.Range(0, ballots.CandidateCount) .Where(s => beatMatrix.Beats(s, b)) .ToList())) .ToList(); var approvalCount = new int[ballots.CandidateCount]; var firstChoices = new int[ballots.CandidateCount]; var compromises = new CountedList <(ulong Preferred, int Compromise, ulong Bogeymen)>(); foreach (var(ballot, count) in ballots.Comparers) { // Always approve of first choices. foreach (var c in ballot.Ranking[0]) { approvalCount[c] += count; firstChoices[c] += count; } // If one prefers all canidates who beat a bogeyman to said bogeyman, do so. // (if we don't, neither will the people who prefer the other saviours -- and we won't be able to beat the bogeyman) // Otherwise, one approve of *all* candidates one likes better than the bogeyman. // Finally, approve the bogeymen we like best, so long as we don't rank them all last. var approveUntilRank = 0; var preferredBogeymenRank = 2 - ballot.Ranking.Count; var preferredBogeymenCoalition = 0ul; var saviourCoalition = 0ul; foreach (var(bogeyman, saviours) in bogeymen) { var bogeymanRank = ballot.RanksByCandidate[bogeyman]; if (bogeymanRank > preferredBogeymenRank) { preferredBogeymenCoalition = GetCoalition(bogeyman); preferredBogeymenRank = bogeymanRank; } var saviourRanks = saviours.Select(s => ballot.RanksByCandidate[s]).ToList(); if (saviours.Any() && saviourRanks.All(sr => sr > bogeymanRank)) { saviourCoalition |= GetCoalition(saviours); approveUntilRank = saviourRanks.Append(approveUntilRank).Min(); } else { approveUntilRank = Math.Min(approveUntilRank, bogeymanRank); if (bogeymanRank == preferredBogeymenRank) { preferredBogeymenCoalition |= GetCoalition(bogeyman); } } } if (approveUntilRank >= preferredBogeymenRank) { approveUntilRank = preferredBogeymenRank; } var approvalCoalition = saviourCoalition | preferredBogeymenCoalition; // Shortcut -- we already approve of rank one. if (approveUntilRank == 0) { continue; } var preferred = GetCoalition(ballot.Ranking[0]); var bogeymenCoalition = GetCoalition(bogeymen .Select(b => b.Bogeyman) .Where(b => ballot.RanksByCandidate[b] <= approveUntilRank && !((approvalCoalition & GetCoalition(b)) > 0))); foreach (var tier in ballot.Ranking.Skip(1)) { foreach (var c in tier) { if (ballot.RanksByCandidate[c] > approveUntilRank || (approvalCoalition & GetCoalition(c)) > 0) { approvalCount[c] += count; compromises.Add((preferred, c, bogeymenCoalition), count); } } preferred |= GetCoalition(tier); } } 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("Bogeymen"); results.AddTable( bogeymen.Select(b => new ElectionResults.Value[] { (ElectionResults.Candidate)b.Bogeyman, b.Saviours }), "Saviours"); 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" ); 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); }
static void Main(string[] args) { bool findDifferences = args.Contains("FindDifferences"); bool findStrategies = args.Contains("FindStrategies"); var voterCountsList = InterestingVoterCounts().ToList(); var votersByCandidateCount = new [] { 4, 5 } .ToDictionary(c => c, c => GetAllVoters(c).ToList().OrderedAtRandom().ToList()); var elections = from candidateCount in votersByCandidateCount.Keys from voterCounts in voterCountsList from permutation in GetUniquePermutations(voterCounts.Length, votersByCandidateCount[candidateCount].Count) select new CandidateComparerCollection <Voter>( candidateCount, permutation .Select((rankingIndex, countIndex) => (votersByCandidateCount[candidateCount][rankingIndex], voterCounts[countIndex])) .ToCountedList() ); var votingMethods = Assembly.GetAssembly(typeof(VotingMethodBase)) .GetTypes() .Where(t => t.IsAssignableTo(typeof(VotingMethodBase)) && !t.IsAbstract && !t.CustomAttributes.Any(a => a.AttributeType == typeof(ObsoleteAttribute))) .Select(t => Activator.CreateInstance(t) as VotingMethodBase) .ToList(); if (args.Length > (findDifferences ? 1 : 0) + (findStrategies ? 1 : 0)) { votingMethods = votingMethods.Where(vm => args.Contains(vm.GetType().Name)).ToList(); if (!votingMethods.Any()) { throw new InvalidOperationException("Args must contain valid voting systems."); } } var effectiveStrategiesFavorite = votingMethods.Count.LengthArray(_ => new CountedList <string>()); var effectiveStrategiesUtility = votingMethods.Count.LengthArray(_ => new CountedList <string>()); int index = 0; var examplesFound = 0; var random = new Random(); Console.WriteLine(); foreach (var ballots in elections) { index++; Console.Write("\r" + index); try { var possibleOutcomes = votingMethods.Select(m => m.GetPossibleResults(ballots)).ToList(); if (findDifferences) { FindDifferences(ballots, possibleOutcomes.Select(m => m.Honest).ToList()); } if (findStrategies) { for (int i = 0; i < votingMethods.Count; i++) { var(favorite, utility) = possibleOutcomes[i].GetPlausibleStrategies(); if (!favorite.Any()) { effectiveStrategiesFavorite[i].Add("Honesty"); } foreach (var strategy in favorite) { effectiveStrategiesFavorite[i].Add(strategy); } if (!utility.Any()) { effectiveStrategiesUtility[i].Add("Honesty"); } foreach (var strategy in utility) { effectiveStrategiesUtility[i].Add(strategy); } if (votingMethods[i].GetType().Name == "BucketConsensusSimple" && utility.Contains("Abstain")) { Console.WriteLine(ballots); } } } } catch (Exception) { Console.WriteLine(); Console.WriteLine(ballots.ToString()); throw; } if (index % 1000 == 0) { GC.Collect(); var sb = new StringBuilder(); sb.AppendLine("Favorite"); AppendTable(effectiveStrategiesFavorite); sb.AppendLine(); sb.AppendLine("Utility"); AppendTable(effectiveStrategiesUtility); Console.Clear(); Console.WriteLine(sb.ToString()); void AppendTable(CountedList <string>[] effectiveStrategies) { var strategies = effectiveStrategies.SelectMany(m => m.Select(a => a.Item)).Distinct().ToList(); ElectionResults.AppendTable(sb, votingMethods.SelectToArray((m, i) => strategies .Select(s => (ElectionResults.Value)(effectiveStrategies[i].TryGetCount(s, out var count) ? (count * 100d / index).ToString("N2") : "")) .Prepend(m.GetType().Name) .ToArray()), strategies.SelectToArray(s => (ElectionResults.Value)s.ToString())); } } } void FindDifferences(CandidateComparerCollection <Voter> voters, List <ElectionResults> results) { if (results.Select(r => RankedConsensusBase.GetCoalition(r.Winners)).Distinct().Count() > 1) { if (random.Next((examplesFound + 10) * (examplesFound + 10)) == 0) { Console.WriteLine(); Console.WriteLine("Voters: " + voters); Console.WriteLine(); for (int i = 0; i < votingMethods.Count; i++) { Console.Write(votingMethods[i].GetType().Name + " "); Console.WriteLine(results[i].Details); Console.WriteLine(); } examplesFound++; } } } }
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; } } for (int first = 0; first < ballots.CandidateCount; first++) { for (int second = 0; second < ballots.CandidateCount; second++) { if (first != second) { int firstApproved = 0; int firstApprovedAndSecondApproved = 0; double secondPreferredCompromisers = 0; foreach (var(ballot, count) in ballots.Comparers) { if (ballot.Buckets[first] != Bucket.Bad) { firstApproved += count; } if (ballot.Buckets[first] != Bucket.Bad && ballot.Buckets[second] != Bucket.Bad) { firstApprovedAndSecondApproved += count; } // We'll evaluate this ballot's contribution to `first` once for each of the candidates `second` it prefers. if (ballot.Buckets[first] == Bucket.Good && ballot.Buckets[second] == Bucket.Best) { secondPreferredCompromisers += count / ballot.Buckets.Count(b => b == Bucket.Best); } } approvalCount[first] += (int)(firstApproved > 0 ? secondPreferredCompromisers * firstApprovedAndSecondApproved / firstApproved : secondPreferredCompromisers); } } } 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."); return(results); }
public override ElectionResults GetElectionResults(CandidateComparerCollection <BucketBallot <Bucket> > ballots) { var approvalCount = new int[ballots.CandidateCount]; var firstChoices = new int[ballots.CandidateCount]; var approvalByBallot = new Dictionary <RankedBallot, ulong>(); // 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 beatMatrix = ballots.GetBeatMatrix(); var candidates = Enumerable.Range(0, ballots.CandidateCount); var compromises = new CountedList <(ulong Preferred, int Compromise, ulong Bogeymen)>(); foreach (var(ballot, count) in ballots.Comparers) { var best = ballot.Buckets.IndexesWhere(a => a == Bucket.Best).ToList(); var good = ballot.Buckets.IndexesWhere(a => a == Bucket.Good).ToList(); var bad = ballot.Buckets.IndexesWhere(a => a == Bucket.Bad).ToList(); // Support each "good" candidate `g` such that // For each "best" candidate `c` // There exists a "bad" candidate `b` (the 'bogeyman') such that `b` beats `c` in first-choice votes but loses to `g` one-on-one if (good.Any() && bad.Any()) { var bogeymen = bad .Where(b => best.All(c => firstChoices[b] > firstChoices[c])) .ToList(); if (bogeymen.Any()) { var bestCoalition = GetCoalition(best); var bogeymenCoalition = GetCoalition(bogeymen); foreach (var c in good.Where(g => bogeymen.Any(b => beatMatrix.Beats(g, b)))) { approvalCount[c] += count; compromises.Add((bestCoalition, c, bogeymenCoalition), 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"); return(results); }
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); }
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 <BucketBallot <Bucket> > ballots) { var beatMatrix = ballots.GetBeatMatrix(); var bogeymen = beatMatrix .GetSchulzeSet() .Select(b => ( Bogeyman: b, Saviours: Enumerable.Range(0, ballots.CandidateCount) .Where(s => beatMatrix.Beats(s, b)) .ToList())) .ToList(); var approvalCount = new int[ballots.CandidateCount]; var firstChoices = new int[ballots.CandidateCount]; var compromises = new CountedList <(ulong Preferred, int Compromise, ulong Bogeymen)>(); foreach (var(ballot, count) in ballots.Comparers) { // Always approve of first choices. foreach (var c in ballot.Buckets.IndexesWhere(b => b == Bucket.Best)) { approvalCount[c] += count; firstChoices[c] += count; } // If one prefers all canidates who beat a bogeyman to said bogeyman, support them all. // (if we don't, neither will the people who prefer the other saviours -- and we won't be able to beat the bogeyman) // Otherwise, support of *all* candidates one likes better than the bogeyman. var bogeymanCoalition = 0ul; var saviourCoalition = 0ul; foreach (var(bogeyman, saviours) in bogeymen) { var bogeymanRank = ballot.Buckets[bogeyman]; if (ballot.Buckets[bogeyman] != Bucket.Bad) { continue; } if (saviours.Any() && saviours.All(s => ballot.Buckets[s] == Bucket.Best)) { continue; } if (saviours.Any() && saviours.All(s => ballot.Buckets[s] != Bucket.Bad)) { bogeymanCoalition |= GetCoalition(bogeyman); saviourCoalition |= GetCoalition(saviours.Where(s => ballot.Buckets[s] == Bucket.Good)); } else { bogeymanCoalition |= GetCoalition(bogeyman); saviourCoalition = GetCoalition(ballot.Buckets.IndexesWhere(b => b == Bucket.Good)); } } if (saviourCoalition == 0ul) { continue; } var best = GetCoalition(ballot.Buckets.IndexesWhere(b => b == Bucket.Best)); foreach (var c in GetCandidates(saviourCoalition)) { approvalCount[c] += count; compromises.Add((best, c, bogeymanCoalition), 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("Bogeymen"); results.AddTable( bogeymen.Select(b => new ElectionResults.Value[] { (ElectionResults.Candidate)b.Bogeyman, b.Saviours }), "Saviours"); 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" ); return(results); }