/// <summary> /// Implementation to generate the ranking list for the provided set /// of votes for a specific task. /// </summary> /// <param name="task">The task that the votes are grouped under.</param> /// <returns>Returns a ranking list of winning votes.</returns> protected override RankResults RankTask(GroupedVotesByTask task) { RankResults winningChoices = new RankResults(); // The groupVotes are used for getting the Wilson score var rankedVotes = GroupRankVotes.GroupByVoteAndRank(task); // The voterRankings are used for the runoff var voterRankings = GroupRankVotes.GroupByVoterAndRank(task); // The full choices list is just to keep track of how many we have left. var allChoices = GroupRankVotes.GetAllChoices(voterRankings); for (int i = 1; i <= 9; i++) { RankResult winner = GetWinningVote(voterRankings, rankedVotes); if (winner == null) { break; } winningChoices.Add(winner); allChoices.Remove(winner.Option); if (!allChoices.Any()) { break; } voterRankings = RemoveChoiceFromVotes(voterRankings, winner.Option); rankedVotes = RemoveChoiceFromRanks(rankedVotes, winner.Option); } return(winningChoices); }
/// <summary> /// Implementation to generate the ranking list for the provided set /// of votes for a specific task. /// </summary> /// <param name="task">The task that the votes are grouped under.</param> /// <returns>Returns a ranking list of winning votes.</returns> protected override RankResults RankTask(GroupedVotesByTask task) { if (task == null) { throw new ArgumentNullException(nameof(task)); } RankResults winningChoices = new RankResults(); if (task.Any()) { var voterRankings = GroupRankVotes.GroupByVoterAndRank(task); var allChoices = GroupRankVotes.GetAllChoices(voterRankings); for (int i = 1; i <= 9; i++) { RankResult winner = GetWinningVote(voterRankings, winningChoices); if (winner == null) { break; } winningChoices.Add(winner); allChoices.Remove(winner.Option); if (!allChoices.Any()) { break; } } } return(winningChoices); }
/// <summary> /// Fills the pairwise preferences. /// This goes through each voter's ranking options and updates an array indicating /// which options are preferred over which other options. Each higher-ranked /// option gains one point in 'beating' a lower-ranked option. /// </summary> /// <param name="voterRankings">The voter rankings.</param> /// <param name="listOfChoices">The list of choices.</param> /// <returns>Returns a filled-in preferences array.</returns> private int[,] GetPairwisePreferences(IEnumerable<VoterRankings> voterRankings, List<string> listOfChoices) { int[,] pairwisePreferences = new int[listOfChoices.Count, listOfChoices.Count]; var choiceIndexes = GroupRankVotes.GetChoicesIndexes(listOfChoices); foreach (var voter in voterRankings) { var rankedChoices = voter.RankedVotes.Select(v => v.Vote); var unrankedChoices = listOfChoices.Except(rankedChoices); foreach (var choice in voter.RankedVotes) { // Each choice matching or beating the ranks of other ranked choices is marked. foreach (var otherChoice in voter.RankedVotes) { if ((choice.Vote != otherChoice.Vote) && (choice.Rank <= otherChoice.Rank)) { pairwisePreferences[choiceIndexes[choice.Vote], choiceIndexes[otherChoice.Vote]]++; } } // Each choice is ranked higher than all unranked choices foreach (var nonChoice in unrankedChoices) { pairwisePreferences[choiceIndexes[choice.Vote], choiceIndexes[nonChoice]]++; } } } return pairwisePreferences; }
/// <summary> /// Gets the least preferred choice. /// This is normally determined by selecting the option with the lowest Borda count. /// This is inverted because we don't want to convert ranks to Borda values (it gains us nothing). /// It then needs to be averaged across the number of instances of each vote, to /// account for unranked options. This allows apples-to-apples comparisons against options /// that are ranked in all votes. /// We then need to scale it relative to the number of instances of that option appearing, to deal /// with truncated rankings (where there are more options than rankings allowed). /// An option ranked infrequently can be scaled up relative to its rate of occurance for /// a high likelihood of elimination. /// </summary> /// <param name="localRankings">The vote rankings.</param> /// <returns>Returns the vote string for the least preferred vote.</returns> private static string GetLeastPreferredChoice(List <VoterRankings> localRankings) { var groupVotes = GroupRankVotes.GroupByVoteAndRank(localRankings); var rankedVotes = from vote in groupVotes select new { Vote = vote.VoteContent, Rank = RankScoring.LowerWilsonScore(vote.Ranks) }; var worstVote = rankedVotes.MinObject(a => a.Rank); Debug.Write($"({worstVote.Rank:f5}) {worstVote.Vote}"); return(worstVote.Vote); }
/// <summary> /// Implementation to generate the ranking list for the provided set /// of votes for a specific task. /// </summary> /// <param name="task">The task that the votes are grouped under.</param> /// <returns>Returns a ranking list of winning votes.</returns> protected override RankResults RankTask(GroupedVotesByTask task) { var groupVotes = GroupRankVotes.GroupByVoteAndRank(task); var rankedVotes = from vote in groupVotes select new { Vote = vote.VoteContent, Rank = RankVote(vote.Ranks) }; var orderedVotes = rankedVotes.OrderByDescending(a => a.Rank); RankResults results = new RankResults(); results.AddRange(orderedVotes.Select(a => new RankResult(a.Vote, $"BordaRank: [{a.Rank}]"))); return(results); }
/// <summary> /// Implementation to generate the ranking list for the provided set /// of votes for a specific task. /// </summary> /// <param name="task">The task that the votes are grouped under.</param> /// <returns>Returns a ranking list of winning votes.</returns> protected override RankResults RankTask(GroupedVotesByTask task) { // Can calculating the score easily by having all the rankings for // each vote grouped together. var groupVotes = GroupRankVotes.GroupByVoteAndRank(task); var rankedVotes = from vote in groupVotes select new { Vote = vote.VoteContent, Rank = RankScoring.LowerWilsonScore(vote.Ranks) }; var orderedVotes = rankedVotes.OrderByDescending(a => a.Rank); RankResults results = new RankResults(); results.AddRange(orderedVotes.Select(a => new RankResult(a.Vote, $"Wilson: [{a.Rank:f5}]"))); return(results); }
/// <summary> /// Fills the pairwise preferences. /// This goes through each voter's ranking options and updates an array indicating /// which options are preferred over which other options. /// Each higher-ranked option gains the difference in ranking in 'beating' a lower-ranked option. /// </summary> /// <param name="voterRankings">The voter rankings.</param> /// <param name="listOfChoices">The list of choices.</param> /// <returns>Returns a filled-in preferences array.</returns> private static DistanceData GetPairwiseData(IEnumerable <VoterRankings> voterRankings, List <string> listOfChoices) { DistanceData data = new DistanceData { Paths = new int[listOfChoices.Count, listOfChoices.Count] }; var choiceIndexes = GroupRankVotes.GetChoicesIndexes(listOfChoices); foreach (var voter in voterRankings) { var rankedChoices = voter.RankedVotes.Select(v => v.Vote); var unrankedChoices = listOfChoices.Except(rankedChoices); foreach (var choice in voter.RankedVotes) { foreach (var otherChoice in voter.RankedVotes) { // Each ranked vote that has a higher rank (lower number) than each // alternative has the distance between the choices added to the // corresponding table entry. if (choice.Vote != otherChoice.Vote && choice.Rank < otherChoice.Rank) { data.Paths[choiceIndexes[choice.Vote], choiceIndexes[otherChoice.Vote]] += otherChoice.Rank - choice.Rank; } } // All unranked options are considered to be at distance 0 from *all* ranked options. // There is no relative preference, nor does it place unranked options 'beneath' // ranked options, such that higher ranked options have greater distance from them. // Unranked options are agnostic choices. //foreach (var nonChoice in unrankedChoices) //{ // //data.Paths[choiceIndexes[choice.Vote], choiceIndexes[nonChoice]]++; //} } // All unranked options are at distance 0 from each other, and thus have no effect // on the distance table. } return(data); }
/// <summary> /// Implementation to generate the ranking list for the provided set /// of votes for a specific task, based on the Schulze algorithm. /// </summary> /// <param name="task">The task that the votes are grouped under.</param> /// <returns>Returns a ranking list of winning votes.</returns> protected override RankResults RankTask(GroupedVotesByTask task) { if (task == null) throw new ArgumentNullException(nameof(task)); Debug.WriteLine(">>Pairwise Ranking<<"); List<string> listOfChoices = GroupRankVotes.GetAllChoices(task); var voterRankings = GroupRankVotes.GroupByVoterAndRank(task); int[,] pairwisePreferences = GetPairwisePreferences(voterRankings, listOfChoices); int[,] pairwiseWinners = GetPairwiseWinners(pairwisePreferences, listOfChoices.Count); RankResults winningChoices = GetResultsInOrder(pairwiseWinners, listOfChoices); return winningChoices; }
/// <summary> /// Implementation to generate the ranking list for the provided set /// of votes for a specific task. /// </summary> /// <param name="task">The task that the votes are grouped under.</param> /// <returns>Returns a ranking list of winning votes.</returns> protected override RankResults RankTask(GroupedVotesByTask task) { Debug.WriteLine(">>Normalized Borda Counting<<"); //var voterCount = task.SelectMany(t => t.Value).Distinct().Count(); var groupVotes = GroupRankVotes.GroupByVoteAndRank(task); var rankedVotes = from vote in groupVotes select new { Vote = vote.VoteContent, Rank = RankVote(vote.Ranks) }; var orderedVotes = rankedVotes.OrderBy(a => a.Rank); RankResults results = new RankResults(); results.AddRange(orderedVotes.Select(a => new RankResult(a.Vote, $"BordaNorm: [{a.Rank:f5}]"))); return(results); }
/// <summary> /// Implementation to generate the ranking list for the provided set /// of votes for a specific task, based on the Schulze algorithm. /// </summary> /// <param name="task">The task that the votes are grouped under.</param> /// <returns>Returns a ranking list of winning votes.</returns> protected override RankResults RankTask(GroupedVotesByTask task) { if (task == null) { throw new ArgumentNullException(nameof(task)); } List <string> listOfChoices = GroupRankVotes.GetAllChoices(task); var voterRankings = GroupRankVotes.GroupByVoterAndRank(task); int[,] pairwisePreferences = GetPairwisePreferences(voterRankings, listOfChoices); int[,] strongestPaths = GetStrongestPaths(pairwisePreferences, listOfChoices.Count); int[,] winningPaths = GetWinningPaths(strongestPaths, listOfChoices.Count); RankResults winningChoices = GetResultsInOrder(winningPaths, listOfChoices); return(winningChoices); }
/// <summary> /// Implementation to generate the ranking list for the provided set /// of votes for a specific task, based on the Schulze algorithm. /// </summary> /// <param name="task">The task that the votes are grouped under.</param> /// <returns>Returns a ranking list of winning votes.</returns> protected override RankResults RankTask(GroupedVotesByTask task) { if (task == null) { throw new ArgumentNullException(nameof(task)); } Debug.WriteLine(">>Distance U0 Scoring<<"); List <string> listOfChoices = GroupRankVotes.GetAllChoices(task); var voterRankings = GroupRankVotes.GroupByVoterAndRank(task); DistanceData pairwiseData = GetPairwiseData(voterRankings, listOfChoices); DistanceData strengthData = GetStrongestPaths(pairwiseData, listOfChoices.Count); DistanceData winningPaths = GetWinningPaths(strengthData, listOfChoices.Count); RankResults winningChoices = GetResultsInOrder(winningPaths, listOfChoices); return(winningChoices); }