-
Notifications
You must be signed in to change notification settings - Fork 10
Borda voting rule added #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
22b0496
e4b0c2c
3f35ad5
1d13531
141b54c
5796eee
dd6873d
5185e44
865bc68
1ecea1e
f38508e
3cc4925
c47cba5
1377cbf
50915b1
47b8218
f55282c
0d79235
433c06e
f81648d
6af08d4
9d1ddb1
be0253a
6faa9c1
e2d3e62
b67743c
c157b8d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # Copyright (C) 2023, Leora Schmerler | ||
| # | ||
| # This program is free software: you can redistribute it and/or modify | ||
| # it under the terms of the GNU General Public License as published by | ||
| # the Free Software Foundation, either version 3 of the License, or | ||
| # (at your option) any later version. | ||
| # | ||
| # This program is distributed in the hope that it will be useful, | ||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| # GNU General Public License for more details. | ||
| # | ||
| # You should have received a copy of the GNU General Public License | ||
| # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
|
||
| from .abstract_classes import AbstractSingleWinnerVotingSystem | ||
| from .borda_at_large import BordaAtLarge | ||
|
|
||
| class Borda(AbstractSingleWinnerVotingSystem): | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add class documentation and doctest.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added. Thank you |
||
| def __init__(self, ballots, tie_breaker=None): | ||
| """ | ||
| The constructer accepts ballots of voters and a tie breaker if given. | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed it does expect ballots and a tie breaker, but the format would be useful to know for anyone consuming this class. You should amend the comment to include a brief description of the expected format of the ballots. |
||
| >>> Borda([{ "count":3, "ballot":["A", "B", "C", "D"]},{ "count":2, "ballot":["D", "B", "A", "C"]},{ "count":2, "ballot":["D", "B", "C", "A"]}]) # doctest:+ELLIPSIS | ||
| <...> | ||
| >>> Borda([{ "count":3, "ballot":["A", "B", "C", "D"]},{ "count":2, "ballot":["D", "B", "A", "C"]},{ "count":2, "ballot":["D", "B", "C", "A"]}], tie_breaker=["A", "B", "C", "D"]) # doctest:+ELLIPSIS | ||
| <...> | ||
| """ | ||
| super(Borda, self).__init__(ballots, BordaAtLarge, tie_breaker=tie_breaker) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| import doctest | ||
| (failures, tests) = doctest.testmod(report=True) | ||
| print("{} failures, {} tests".format(failures, tests)) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| # Copyright (C) 2023, Leora Schmerler | ||
| # | ||
| # This program is free software: you can redistribute it and/or modify | ||
| # it under the terms of the GNU General Public License as published by | ||
| # the Free Software Foundation, either version 3 of the License, or | ||
| # (at your option) any later version. | ||
| # | ||
| # This program is distributed in the hope that it will be useful, | ||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| # GNU General Public License for more details. | ||
| # | ||
| # You should have received a copy of the GNU General Public License | ||
| # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
|
||
| from .abstract_classes import MultipleWinnerVotingSystem | ||
| from .common_functions import matching_keys, unique_permutations | ||
| import copy | ||
|
|
||
| class BordaAtLarge(MultipleWinnerVotingSystem): | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add class documentation and doctest.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added. Thank you |
||
| def __init__(self, ballots: list, tie_breaker=None, required_winners: int=1): | ||
| """ | ||
| The constructer accepts ballots of voters, a tie breaker if given and the number of required winners. | ||
| >>> BordaAtLarge([{ "count":3, "ballot":["A", "B", "C", "D"]},{ "count":2, "ballot":["D", "B", "A", "C"]},{ "count":2, "ballot":["D", "B", "C", "A"]}], required_winners = 2) # doctest:+ELLIPSIS | ||
| <...> | ||
| >>> BordaAtLarge([{ "count":3, "ballot":["A", "B", "C", "D"]},{ "count":2, "ballot":["D", "B", "A", "C"]},{ "count":2, "ballot":["D", "B", "C", "A"]}], tie_breaker=["A", "B", "C", "D"] ,required_winners = 2) # doctest:+ELLIPSIS | ||
| <...> | ||
| """ | ||
| super(BordaAtLarge, self).__init__(ballots, tie_breaker=tie_breaker, required_winners=required_winners) | ||
|
|
||
| def calculate_results(self): | ||
| """ | ||
| calculate_results accepts an instance of Borda, and is called from the constructor. | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typically you would not state that it accepts anything if the only argument is the class instance itself. This should be reworded to describe what the function actually does. |
||
| >>> BordaAtLarge([{ "count":3, "ballot":["A", "B", "C", "D"]},{ "count":2, "ballot":["D", "B", "A", "C"]},{ "count":2, "ballot":["D", "B", "C", "A"]}], tie_breaker=["A", "B", "C", "D"] ,required_winners = 2).calculate_results() # doctest:+ELLIPSIS | ||
| ... | ||
| >>> BordaAtLarge([{ "count":3, "ballot":["A", "B", "C", "D"]},{ "count":2, "ballot":["D", "B", "A", "C"]},{ "count":2, "ballot":["D", "B", "C", "A"]}], required_winners = 2).calculate_results() # doctest:+ELLIPSIS | ||
| ... | ||
| """ | ||
|
|
||
| # Standardize the ballot format and extract the candidates | ||
| self.candidates = set() | ||
| for ballot in self.ballots: | ||
|
|
||
| # Convert a single candidate ballots into ballot lists | ||
| if not isinstance(ballot["ballot"], list): | ||
| ballot["ballot"] = [ballot["ballot"]] | ||
|
|
||
| # Ensure no ballot has an excess of votes | ||
| if len(ballot["ballot"]) < self.required_winners: | ||
| raise Exception("A ballot contained too many candidates") | ||
|
|
||
| # Add all candidates on the ballot to the set | ||
| self.candidates.update(set(ballot["ballot"])) | ||
|
|
||
| # Sum up all votes for each candidate | ||
| self.tallies = dict.fromkeys(self.candidates, 0) | ||
| for ballot in self.ballots: | ||
| for i in range(len(ballot["ballot"])): | ||
| self.tallies[ballot["ballot"][i]] += (ballot["count"]*(len(self.candidates)-1-i)) | ||
| tallies = copy.deepcopy(self.tallies) | ||
|
|
||
| # Determine which candidates win | ||
| winning_candidates = set() | ||
| while len(winning_candidates) < self.required_winners: | ||
|
|
||
| # Find the remaining candidates with the most votes | ||
| largest_tally = max(tallies.values()) | ||
| top_candidates = matching_keys(tallies, largest_tally) | ||
|
|
||
| # Reduce the found candidates if there are too many | ||
| if len(top_candidates | winning_candidates) > self.required_winners: | ||
| self.tied_winners = top_candidates.copy() | ||
| while len(top_candidates | winning_candidates) > self.required_winners: | ||
| top_candidates.remove(self.break_ties(top_candidates, True)) | ||
|
|
||
| # Move the top candidates into the winning pile | ||
| winning_candidates |= top_candidates | ||
| for candidate in top_candidates: | ||
| del tallies[candidate] | ||
|
|
||
| self.winners = winning_candidates | ||
|
|
||
| def as_dict(self): | ||
| """ | ||
| as_dict accepts an instance of Borda, and returns a dict of the BordaAtLarge. | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above, reword docstring. |
||
| >>> BordaAtLarge([{ "count":3, "ballot":["A", "B", "C", "D"]},{ "count":2, "ballot":["D", "B", "A", "C"]},{ "count":2, "ballot":["D", "B", "C", "A"]}], tie_breaker=["A", "B", "C", "D"] ,required_winners = 2).as_dict() # doctest:+ELLIPSIS | ||
| {...} | ||
| >>> BordaAtLarge([{ "count":3, "ballot":["A", "C", "B", "D"]},{ "count":2, "ballot":["D", "B", "A", "C"]},{ "count":2, "ballot":["D", "B", "C", "A"]}], required_winners = 2).as_dict() # doctest:+ELLIPSIS | ||
| {...} | ||
| """ | ||
| data = super(BordaAtLarge, self).as_dict() | ||
| data["tallies"] = self.tallies | ||
| return data | ||
|
|
||
| if __name__ == '__main__': | ||
| import doctest | ||
| (failures, tests) = doctest.testmod(report=True) | ||
| print("{} failures, {} tests".format(failures, tests)) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
name should be added to
CONTRIBUTORSat the root of the repo.