// A simple count of who has the most votes. /// <inheritdoc/> protected override void CountBallot(CountedBallot ballot) { // Only counts ballots for hopeful and elected candidates Dictionary <Candidate, CandidateState> candidates = candidateStates .Where(x => new[] { CandidateState.States.hopeful, CandidateState.States.elected } .Contains(x.Value.State)) .ToDictionary(x => x.Key, x => x.Value); Vote vote = null; foreach (Vote v in ballot.Votes) { // Skip candidates not included in this count. if (!candidates.Keys.Contains(v.Candidate)) { continue; } // First vote examined or it beats current if (vote is null || v.Beats(vote)) { vote = v; } } if (!(vote is null)) { candidateStates[vote.Candidate].VoteCount += ballot.Count; }
/// <inheritdoc/> protected override void CountBallot(CountedBallot ballot) { decimal weight = 1.0m; List <Vote> votes = ballot.Votes.ToList(); // Ensure any newly-seen candidates are counted List <Candidate> c = ballot.Votes.Where(x => !candidateStates.Keys.Contains(x.Candidate)) .Select(x => x.Candidate).ToList(); InitializeCandidateStates(c); votes.Sort(); foreach (Vote v in votes) { MeekCandidateState cs = candidateStates[v.Candidate] as MeekCandidateState; if (cs is null) { throw new InvalidOperationException("candidateState contains objects that aren't of type MeekCandidateState"); } // Get value to transfer to this candidate, restricted to // the specified precision decimal value = weight * cs.KeepFactor; weight = RoundUp(weight); // Add this to the candidate's vote and remove from ballot's // weight. CountedBallot shows multiple identical ballots, so // we add that many ballots to the vote and decrease the weight // of all identical ballots by the value kept. cs.VoteCount += value * ballot.Count; weight -= value; // Do this until weight hits zero, or we run out of rankings. // Note: Already-elected candidates have keep factors between // 0 and 1; hopefuls all have 1; defeated will have 0. The // remaining voting power is either all transfered to the first // hopeful candidate or exhausted as the ballot runs out of // non-defeated candidates. // // We only hit 0 if a non-elected hopeful is on the ballot. if (weight <= 0.0m) { break; } } }
/// <summary> /// Condenses an enumerable of Ballots into a BallotSet. Combines duplicate /// Ballots and CountedBallots into single CountedBallots. /// </summary> /// <param name="ballots">The Ballots to return as a set.</param> /// <returns>A BallotSet with any duplicate Ballots combined into CountedBallots.</returns> public BallotSet CreateBallotSet(IEnumerable <Ballot> ballots) { HashSet <Ballot> outBallots = new HashSet <Ballot>(); Dictionary <Ballot, long> ballotCounts = new Dictionary <Ballot, long>(); int threadCount = Environment.ProcessorCount; // We need to create and count each single, identical ballot, // and count the number of such ballots in any CountedBallot we // encounter. To do this, we create uncounted, single ballots // and specifically avoid looking up CountedBallot. void CountBallots() { List <Ballot> bList = ballots.ToList(); Dictionary <Ballot, long>[] subsets = new Dictionary <Ballot, long> [threadCount]; // Thread safety: only writes to function-local objects; // reads from an index in bList. Dictionary <Ballot, long> CountSubsets(int start, int end) { Dictionary <Ballot, long> bC = new Dictionary <Ballot, long>(); for (int i = start; i <= end; i++) { Ballot oneBallot = CreateBallot(bList[i].Votes); long count = (bList[i] is CountedBallot) ? (bList[i] as CountedBallot).Count : 1; if (!bC.ContainsKey(oneBallot)) { bC[oneBallot] = 0; } bC[oneBallot] += count; } return(bC); } // First divide all the processes up for background run // Thread safety: subsets is an array not accessed outside this loop; // each parallel thread accesses a specific unique index in the array. Parallel.For(0, threadCount, i => { subsets[i] = CountSubsets(bList.Count() * i / threadCount, (bList.Count() * (i + 1) / threadCount) - 1); }); // Now merge them for (int i = 0; i < threadCount; i++) { foreach (Ballot b in subsets[i].Keys) { // Count this in the full ballot counts if (!ballotCounts.ContainsKey(b)) { ballotCounts[b] = 0; } ballotCounts[b] += subsets[i][b]; // Check for identical ballots found in each further thread for (int j = i + 1; j < threadCount; j++) { if (subsets[j].ContainsKey(b)) { ballotCounts[b] += subsets[j][b]; // It's been counted, so remove it subsets[j].Remove(b); } } } // We've counted all these, so clear them. subsets[i].Clear(); } } // Count in a threaded model CountBallots(); // Generate CountedBallots from the counts made foreach (Ballot b in ballotCounts.Keys) { Ballot newBallot; if (ballotCounts[b] == 1) { newBallot = b; } else { newBallot = new CountedBallot(b, ballotCounts[b]); } // Look itself up to store or deduplicate newBallot = Ballots[newBallot]; outBallots.Add(newBallot); } return(new BallotSet(outBallots)); }
/// <summary> /// Count ballot and update candidateState. /// </summary> /// <param name="ballot">The ballot to count.</param> protected abstract void CountBallot(CountedBallot ballot);
/// <summary> /// Converts a set of candidates and ballots to a graph of wins and ties. /// </summary> /// <param name="ballots">Ranked ballots in the election.</param> public PairwiseGraph(BallotSet ballots) { // Initialize candidate graph HashSet <Candidate> candidates = (ballots as IEnumerable <CountedBallot>).SelectMany(x => x.Votes.Select(y => y.Candidate)).ToHashSet(); foreach (Candidate c in candidates) { nodes[c] = new Dictionary <Candidate, decimal>(); foreach (Candidate d in candidates.Except(new[] { c })) { nodes[c][d] = 0.0m; } } void BuildGraph() { List <CountedBallot> bList = ballots.ToList(); int threadCount = Environment.ProcessorCount; PairwiseGraph[] subsets = new PairwiseGraph[threadCount]; PairwiseGraph CountSubsets(int start, int end) { PairwiseGraph g = new PairwiseGraph(); foreach (Candidate c in candidates) { g.nodes[c] = new Dictionary <Candidate, decimal>(); foreach (Candidate d in candidates.Except(new[] { c })) { g.nodes[c][d] = 0.0m; } } // Iterate each ballot and count who wins and who ties. // This can support tied ranks and each ballot is O(SUM(1..n)) and o(n). //foreach (CountedBallot b in ballots) for (int i = start; i <= end; i++) { CountedBallot b = bList[i]; HashSet <Candidate> ranked = b.Votes.Select(x => x.Candidate).ToHashSet(); HashSet <Candidate> unranked = candidates.Except(ranked).ToHashSet(); // Iterate to compare each pair. Stack <Vote> votes = new Stack <Vote>(b.Votes); while (votes.Count > 0) { Vote v = votes.Pop(); foreach (Vote u in votes) { // Who is ranked first? No action if a tie. if (v.Beats(u)) { g.nodes[v.Candidate][u.Candidate] += b.Count; } else if (u.Beats(v)) { g.nodes[u.Candidate][v.Candidate] += b.Count; } } // Defeat all unranked candidates foreach (Candidate c in unranked) { g.nodes[v.Candidate][c] += b.Count; } } } return(g); } // First divide all the processes up for background run Parallel.For(0, threadCount, (i, state) => subsets[i] = CountSubsets(bList.Count() * i / threadCount, (bList.Count() * (i + 1) / threadCount) - 1)); // Add them all together foreach (PairwiseGraph g in subsets) { AddGraph(g); } } BuildGraph(); }