Example #1
0
        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);
        }
Example #4
0
        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);
        }
Example #5
0
        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);
        }