pref_voting 1.16.31__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pref_voting/__init__.py +1 -0
- pref_voting/analysis.py +496 -0
- pref_voting/axiom.py +38 -0
- pref_voting/axiom_helpers.py +129 -0
- pref_voting/axioms.py +10 -0
- pref_voting/c1_methods.py +963 -0
- pref_voting/combined_methods.py +514 -0
- pref_voting/create_methods.py +128 -0
- pref_voting/data/examples/condorcet_winner/minimal_Anti-Plurality.soc +16 -0
- pref_voting/data/examples/condorcet_winner/minimal_Borda.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_Bracket_Voting.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_Bucklin.soc +19 -0
- pref_voting/data/examples/condorcet_winner/minimal_Coombs.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_Coombs_PUT.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_Coombs_TB.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_Dowdall.soc +19 -0
- pref_voting/data/examples/condorcet_winner/minimal_Instant_Runoff.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_Instant_Runoff_PUT.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_Instant_Runoff_TB.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_Iterated_Removal_Condorcet_Loser.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_Pareto.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_Plurality.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_PluralityWRunoff_PUT.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_Positive-Negative_Voting.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_Simplified_Bucklin.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_Superior_Voting.soc +19 -0
- pref_voting/data/examples/condorcet_winner/minimal_Weighted_Bucklin.soc +19 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Anti-Plurality.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Borda.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Bracket_Voting.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Bucklin.soc +19 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Coombs.soc +21 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Coombs_PUT.soc +21 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Coombs_TB.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Dowdall.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Instant_Runoff.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Instant_Runoff_PUT.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Instant_Runoff_TB.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Plurality.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_PluralityWRunoff_PUT.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Positive-Negative_Voting.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Simplified_Bucklin.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Weighted_Bucklin.soc +19 -0
- pref_voting/data/voting_methods_properties.json +414 -0
- pref_voting/data/voting_methods_properties.json.lock +0 -0
- pref_voting/dominance_axioms.py +387 -0
- pref_voting/generate_profiles.py +801 -0
- pref_voting/generate_spatial_profiles.py +198 -0
- pref_voting/generate_utility_profiles.py +160 -0
- pref_voting/generate_weighted_majority_graphs.py +506 -0
- pref_voting/grade_methods.py +184 -0
- pref_voting/grade_profiles.py +357 -0
- pref_voting/helper.py +370 -0
- pref_voting/invariance_axioms.py +671 -0
- pref_voting/io/__init__.py +0 -0
- pref_voting/io/readers.py +432 -0
- pref_voting/io/writers.py +256 -0
- pref_voting/iterative_methods.py +2425 -0
- pref_voting/maj_graph_ex1.png +0 -0
- pref_voting/mappings.py +577 -0
- pref_voting/margin_based_methods.py +2345 -0
- pref_voting/monotonicity_axioms.py +872 -0
- pref_voting/num_evaluation_method.py +77 -0
- pref_voting/other_axioms.py +161 -0
- pref_voting/other_methods.py +939 -0
- pref_voting/pairwise_profiles.py +547 -0
- pref_voting/prob_voting_method.py +105 -0
- pref_voting/probabilistic_methods.py +287 -0
- pref_voting/profiles.py +856 -0
- pref_voting/profiles_with_ties.py +1069 -0
- pref_voting/rankings.py +466 -0
- pref_voting/scoring_methods.py +481 -0
- pref_voting/social_welfare_function.py +59 -0
- pref_voting/social_welfare_functions.py +7 -0
- pref_voting/spatial_profiles.py +448 -0
- pref_voting/stochastic_methods.py +99 -0
- pref_voting/strategic_axioms.py +1394 -0
- pref_voting/swf_axioms.py +173 -0
- pref_voting/utility_functions.py +102 -0
- pref_voting/utility_methods.py +178 -0
- pref_voting/utility_profiles.py +333 -0
- pref_voting/variable_candidate_axioms.py +640 -0
- pref_voting/variable_voter_axioms.py +3747 -0
- pref_voting/voting_method.py +355 -0
- pref_voting/voting_method_properties.py +92 -0
- pref_voting/voting_methods.py +8 -0
- pref_voting/voting_methods_registry.py +136 -0
- pref_voting/weighted_majority_graphs.py +1539 -0
- pref_voting-1.16.31.dist-info/METADATA +208 -0
- pref_voting-1.16.31.dist-info/RECORD +92 -0
- pref_voting-1.16.31.dist-info/WHEEL +4 -0
- pref_voting-1.16.31.dist-info/licenses/LICENSE.txt +21 -0
pref_voting/rankings.py
ADDED
@@ -0,0 +1,466 @@
|
|
1
|
+
"""
|
2
|
+
File: profiles_with_ties.py
|
3
|
+
Author: Wes Holliday (wesholliday@berkeley.edu) and Eric Pacuit (epacuit@umd.edu)
|
4
|
+
Date: January 5, 2022
|
5
|
+
Updated: July 13, 2022
|
6
|
+
Updated: December 19, 2022
|
7
|
+
|
8
|
+
Functions to reason about rankings of candidates.
|
9
|
+
"""
|
10
|
+
|
11
|
+
import copy
|
12
|
+
from tabulate import tabulate
|
13
|
+
|
14
|
+
class Ranking(object):
|
15
|
+
"""A ranking of a set of candidates.
|
16
|
+
|
17
|
+
A ranking is a map from candidates to ranks (integers). There is no assumption that all candidates in an election are ranked.
|
18
|
+
|
19
|
+
:param rmap: Dictionary in which the keys are the candidates and the values are the ranks.
|
20
|
+
:type rmap: dict[int or str: int]
|
21
|
+
:param cmap: Dictionary mapping candidates (keys of the ``rmap``) to candidate names (strings). If not provided, each candidate is mapped to itself.
|
22
|
+
:type cmap: dict[int: str], optional
|
23
|
+
|
24
|
+
:Example:
|
25
|
+
|
26
|
+
The following code creates three rankings:
|
27
|
+
|
28
|
+
1. ``rank1`` is the ranking where 0 is ranked first, 2 is ranked in second-place, and 1 is ranked last.
|
29
|
+
2. ``rank2`` is the ranking where 0 and 1 are tied for first place, and 2 is ranked last.
|
30
|
+
3. ``rank3`` is the ranking where 0 is ranked first, and 2 is ranked in last place.
|
31
|
+
|
32
|
+
.. code-block:: python
|
33
|
+
|
34
|
+
rank1 = Ranking({0:1, 1:3, 2:2})
|
35
|
+
rank2 = Ranking({0:1, 1:1, 2:2})
|
36
|
+
rank3 = Ranking({0:1, 2:3})
|
37
|
+
|
38
|
+
.. important::
|
39
|
+
The numerical value of the ranks do not mean anything. They are only used to make ordinal comparisons. For instance, each of the following represents the same ranking:
|
40
|
+
0 is ranked first, 2 is ranked second, and 1 is ranked in last place.
|
41
|
+
|
42
|
+
.. code-block:: python
|
43
|
+
|
44
|
+
rank1 = Ranking({0:1, 1:3, 2:2})
|
45
|
+
rank2 = Ranking({0:1, 1:10, 2:3})
|
46
|
+
rank3 = Ranking({0:10, 1:100, 2:30})
|
47
|
+
|
48
|
+
"""
|
49
|
+
|
50
|
+
def __init__(self, rmap, cmap=None):
|
51
|
+
"""Constructor method"""
|
52
|
+
|
53
|
+
self.rmap = rmap
|
54
|
+
self.cmap = cmap if cmap is not None else {c: str(c) for c in rmap.keys()}
|
55
|
+
|
56
|
+
@property
|
57
|
+
def ranks(self):
|
58
|
+
"""Returns a sorted list of the ranks."""
|
59
|
+
return sorted(set(self.rmap.values()))
|
60
|
+
|
61
|
+
@property
|
62
|
+
def cands(self):
|
63
|
+
"""Returns a sorted list of the candidates that are ranked."""
|
64
|
+
return sorted(list(self.rmap.keys()))
|
65
|
+
|
66
|
+
def num_ranked_candidates(self):
|
67
|
+
"""Returns the number of ranked candidates"""
|
68
|
+
return len(self.cands)
|
69
|
+
|
70
|
+
def is_bullet_vote(self):
|
71
|
+
"""Return True if the ranking is a bullet vote (a vote for a single candidate)"""
|
72
|
+
|
73
|
+
return self.num_ranked_candidates() == 1
|
74
|
+
|
75
|
+
def cands_at_rank(self, r):
|
76
|
+
"""Returns a list of the candidates that are assigned the rank ``r``."""
|
77
|
+
return [c for c in self.rmap.keys() if self.rmap[c] == r]
|
78
|
+
|
79
|
+
def is_ranked(self, c):
|
80
|
+
"""Returns True if the candidate ``c`` is ranked."""
|
81
|
+
|
82
|
+
return c in self.rmap.keys()
|
83
|
+
|
84
|
+
def strict_pref(self, c1, c2):
|
85
|
+
"""Returns True if ``c1`` is strictly preferred to ``c2``.
|
86
|
+
|
87
|
+
The return value is True when both ``c1`` and ``c2`` are ranked and the rank of ``c1`` is strictly smaller than the rank of ``c2``.
|
88
|
+
"""
|
89
|
+
|
90
|
+
return (self.is_ranked(c1) and self.is_ranked(c2)) and self.rmap[
|
91
|
+
c1
|
92
|
+
] < self.rmap[c2]
|
93
|
+
|
94
|
+
def extended_strict_pref(self, c1, c2):
|
95
|
+
"""Returns True when either ``c1`` is ranked and ``c2`` is not ranked or the rank of ``c1`` is strictly smaller than the rank of ``c2``."""
|
96
|
+
|
97
|
+
return (self.is_ranked(c1) and not self.is_ranked(c2)) or (
|
98
|
+
(self.is_ranked(c1) and self.is_ranked(c2))
|
99
|
+
and self.rmap[c1] < self.rmap[c2]
|
100
|
+
)
|
101
|
+
|
102
|
+
def indiff(self, c1, c2):
|
103
|
+
"""Returns True if ``c1`` and ``c2`` are tied.
|
104
|
+
|
105
|
+
The return value is True when both ``c1`` and ``c2`` are ranked and the rank of ``c1`` equals the rank of ``c2``.
|
106
|
+
|
107
|
+
"""
|
108
|
+
|
109
|
+
return (
|
110
|
+
self.is_ranked(c1) and self.is_ranked(c2) and self.rmap[c1] == self.rmap[c2]
|
111
|
+
)
|
112
|
+
|
113
|
+
def extended_indiff(self, c1, c2):
|
114
|
+
"""Returns True when either both ``c1`` and ``c2`` are not ranked or the rank of ``c1`` equals the rank of ``c2``."""
|
115
|
+
|
116
|
+
return (not self.is_ranked(c1) and not self.is_ranked(c2)) or (
|
117
|
+
self.is_ranked(c1) and self.is_ranked(c2) and self.rmap[c1] == self.rmap[c2]
|
118
|
+
)
|
119
|
+
|
120
|
+
def weak_pref(self, c1, c2):
|
121
|
+
"""Returns True if ``c1`` is weakly preferred to ``c2``.
|
122
|
+
|
123
|
+
The return value is True if either ``c1`` is tied with ``c2`` or ``c1`` is strictly preferred to ``c2``.
|
124
|
+
"""
|
125
|
+
|
126
|
+
return self.strict_pref(c1, c2) or self.indiff(c1, c2)
|
127
|
+
|
128
|
+
def extended_weak_pref(self, c1, c2):
|
129
|
+
"""Returns True when either ``c1`` and ``c2`` are in the relation of extended indifference or ``c1`` is extended strictly preferred to ``c2``."""
|
130
|
+
|
131
|
+
return self.extended_strict_pref(c1, c2) or self.extended_indiff(c1, c2)
|
132
|
+
|
133
|
+
def remove_cand(self, a):
|
134
|
+
"""Returns a Ranking with the candidate ``a`` removed."""
|
135
|
+
|
136
|
+
new_rmap = {c: self.rmap[c] for c in self.rmap.keys() if c != a}
|
137
|
+
new_cmap = {c: self.cmap[c] for c in self.cmap.keys() if c != a}
|
138
|
+
return Ranking(new_rmap, cmap=new_cmap)
|
139
|
+
|
140
|
+
def first(self, cs=None):
|
141
|
+
"""Returns the list of candidates from ``cs`` that have the highest ranking. If ``cs`` is None, then use all the ranked candidates."""
|
142
|
+
|
143
|
+
_ranks = list(self.rmap.values()) if cs is None else [self.rmap[c] for c in cs if c in self.rmap.keys()]
|
144
|
+
_cands = list(self.rmap.keys()) if cs is None else cs
|
145
|
+
min_rank = min(_ranks) if len(_ranks) > 0 else None
|
146
|
+
return sorted([c for c in _cands if c in self.rmap.keys() and self.rmap[c] == min_rank])
|
147
|
+
|
148
|
+
def last(self, cs=None):
|
149
|
+
"""Returns the list of candidates from ``cs`` that have the worst ranking. If ``cs`` is None, then use all the ranked candidates."""
|
150
|
+
|
151
|
+
_ranks = list(self.rmap.values()) if cs is None else [self.rmap[c] for c in cs if c in self.rmap.keys()]
|
152
|
+
_cands = list(self.rmap.keys()) if cs is None else cs
|
153
|
+
max_rank = max(_ranks) if len(_ranks) > 0 else None
|
154
|
+
return sorted([c for c in _cands if c in self.rmap.keys() and self.rmap[c] == max_rank])
|
155
|
+
|
156
|
+
def is_empty(self):
|
157
|
+
"""Return True when the ranking is empty."""
|
158
|
+
return len(self.rmap.keys()) == 0
|
159
|
+
|
160
|
+
def has_tie(self):
|
161
|
+
"""Return True when the ranking has a tie."""
|
162
|
+
return len(list(set(self.rmap.values()))) != len(list(self.rmap.values()))
|
163
|
+
|
164
|
+
def is_tied(self, cands):
|
165
|
+
"""Return True if the ranking contains a tie between the candidates in cands
|
166
|
+
"""
|
167
|
+
return set(self.cands_at_rank(self.rmap[cands[0]])) == set(cands)
|
168
|
+
|
169
|
+
def is_linear(self, num_cands):
|
170
|
+
"""Return True when the ranking is a linear order of ``num_cands`` candidates.
|
171
|
+
"""
|
172
|
+
|
173
|
+
return not self.has_tie() and len(self.rmap.keys()) == num_cands
|
174
|
+
|
175
|
+
def to_linear(self):
|
176
|
+
"""
|
177
|
+
If the ranking has no ties, return
|
178
|
+
a tuple representing the ranking; otherwise, return None.
|
179
|
+
"""
|
180
|
+
if self.has_tie():
|
181
|
+
return None
|
182
|
+
else:
|
183
|
+
return tuple([c for c, r in sorted(self.rmap.items(), key=lambda x: x[1])])
|
184
|
+
|
185
|
+
def is_truncated_linear(self, num_cands):
|
186
|
+
"""Return True when the ranking is a truncated linear order, so it is linear but ranks fewer than ``num_cands`` candidates.
|
187
|
+
"""
|
188
|
+
return not self.has_tie() and len(self.rmap.keys()) < num_cands
|
189
|
+
|
190
|
+
def has_skipped_rank(self):
|
191
|
+
"""Returns True when a rank is skipped."""
|
192
|
+
|
193
|
+
return len(self.ranks) != 0 and self.ranks != list(range(1, len(self.ranks) + 1))
|
194
|
+
|
195
|
+
def has_overvote(self):
|
196
|
+
"""
|
197
|
+
Return True if the voter submitted an overvote (a ranking with a tie).
|
198
|
+
"""
|
199
|
+
return self.has_tie()
|
200
|
+
|
201
|
+
def truncate_overvote(self):
|
202
|
+
"""
|
203
|
+
Truncate the ranking at an overvote.
|
204
|
+
"""
|
205
|
+
|
206
|
+
new_rmap = dict()
|
207
|
+
|
208
|
+
for r in self.ranks:
|
209
|
+
cands_at_rank = self.cands_at_rank(r)
|
210
|
+
if len(cands_at_rank) == 1:
|
211
|
+
new_rmap[cands_at_rank[0]] = r
|
212
|
+
elif len(cands_at_rank) > 1:
|
213
|
+
break
|
214
|
+
|
215
|
+
self.rmap = new_rmap
|
216
|
+
|
217
|
+
def normalize_ranks(self):
|
218
|
+
"""Change the ranks so that they start with 1, and the next rank is the next integer after the previous rank.
|
219
|
+
|
220
|
+
:Example:
|
221
|
+
|
222
|
+
.. exec_code:: python
|
223
|
+
|
224
|
+
from pref_voting.profiles_with_ties import Ranking
|
225
|
+
r = Ranking({0:1, 1:3, 2:2})
|
226
|
+
print(r.rmap)
|
227
|
+
r.normalize_ranks()
|
228
|
+
print("After normalizing: ", r.rmap)
|
229
|
+
|
230
|
+
r = Ranking({0:1, 1:10, 2:3})
|
231
|
+
print(r.rmap)
|
232
|
+
r.normalize_ranks()
|
233
|
+
print("After normalizing: ", r.rmap)
|
234
|
+
|
235
|
+
r = Ranking({0:-100, 1:123, 2:0})
|
236
|
+
print(r.rmap)
|
237
|
+
r.normalize_ranks()
|
238
|
+
print("After normalizing: ", r.rmap)
|
239
|
+
|
240
|
+
r = Ranking({0:10, 1:10, 2:100})
|
241
|
+
print(r.rmap)
|
242
|
+
r.normalize_ranks()
|
243
|
+
print("After normalizing: ", r.rmap)
|
244
|
+
|
245
|
+
"""
|
246
|
+
self.rmap = {c: self.ranks.index(r) + 1 for c, r in self.rmap.items()}
|
247
|
+
|
248
|
+
|
249
|
+
def AAdom(self, c1s, c2s, use_extended_preferences=False):
|
250
|
+
"""
|
251
|
+
Returns True if every candidate in ``c1s`` is weakly preferred to every candidate in ``c2s``. If ``use_extended_preferences`` is True, then use the extended weak preference.
|
252
|
+
"""
|
253
|
+
|
254
|
+
weak_pref = (
|
255
|
+
self.extended_weak_pref if use_extended_preferences else self.weak_pref
|
256
|
+
)
|
257
|
+
|
258
|
+
return all([all([weak_pref(c1, c2) for c2 in c2s]) for c1 in c1s])
|
259
|
+
|
260
|
+
def strong_dom(self, c1s, c2s, use_extended_preferences=False):
|
261
|
+
"""
|
262
|
+
Returns True if ``AAdom(c1s, c2s)`` and there is some candidate in ``c1s`` that is strictly preferred to every candidate in ``c2s``. If ``use_extended_preferences`` is True, then use the extended preferences.
|
263
|
+
"""
|
264
|
+
|
265
|
+
strict_pref = (
|
266
|
+
self.extended_strict_pref if use_extended_preferences else self.strict_pref
|
267
|
+
)
|
268
|
+
|
269
|
+
return self.AAdom(
|
270
|
+
c1s, c2s, use_extended_preferences=use_extended_preferences
|
271
|
+
) and any([all([strict_pref(c1, c2) for c2 in c2s]) for c1 in c1s])
|
272
|
+
|
273
|
+
def weak_dom(self, c1s, c2s, use_extended_preferences=False):
|
274
|
+
"""
|
275
|
+
Returns True if ``AAdom(c1s, c2s)`` and there is some candidate in ``c1s`` that is strictly preferred to some candidate in ``c2s``. If ``use_extended_preferences`` is True, then use the extended preferences.
|
276
|
+
"""
|
277
|
+
|
278
|
+
strict_pref = (
|
279
|
+
self.extended_strict_pref if use_extended_preferences else self.strict_pref
|
280
|
+
)
|
281
|
+
|
282
|
+
return self.AAdom(
|
283
|
+
c1s, c2s, use_extended_preferences=use_extended_preferences
|
284
|
+
) and any([any([strict_pref(c1, c2) for c2 in c2s]) for c1 in c1s])
|
285
|
+
|
286
|
+
def to_indiff_list(self):
|
287
|
+
"""
|
288
|
+
Returns the ranking as a tuple of indifference classes (represented as a tuple).
|
289
|
+
"""
|
290
|
+
return tuple([tuple(self.cands_at_rank(r)) for r in self.ranks])
|
291
|
+
|
292
|
+
@classmethod
|
293
|
+
def from_indiff_list(cls, indiff_list, cmap=None):
|
294
|
+
"""
|
295
|
+
Returns a ranking from a list of indifference classes.
|
296
|
+
"""
|
297
|
+
rmap = dict()
|
298
|
+
for r, cands in enumerate(indiff_list):
|
299
|
+
for c in cands:
|
300
|
+
rmap[c] = r + 1
|
301
|
+
return Ranking(rmap, cmap=cmap)
|
302
|
+
|
303
|
+
@classmethod
|
304
|
+
def from_linear_order(cls, linear_order, cmap=None):
|
305
|
+
"""
|
306
|
+
Returns a ranking from a list of indifference classes.
|
307
|
+
"""
|
308
|
+
rmap = dict()
|
309
|
+
for r, c in enumerate(linear_order):
|
310
|
+
rmap[c] = r + 1
|
311
|
+
return Ranking(rmap, cmap=cmap)
|
312
|
+
|
313
|
+
def to_weak_order(self, candidates):
|
314
|
+
"""
|
315
|
+
Returns the ranking as a weak order over the candidates in the list ``candidates``.
|
316
|
+
"""
|
317
|
+
max_rank = max(self.ranks)
|
318
|
+
new_ranks = self.rmap
|
319
|
+
for c in candidates:
|
320
|
+
if not self.is_ranked(c):
|
321
|
+
new_ranks[c] = max_rank + 1
|
322
|
+
|
323
|
+
new_cmap = {c: self.cmap[c] if c in self.cmap.keys() else f'{c}' for c in candidates}
|
324
|
+
return Ranking(new_ranks, cmap=new_cmap)
|
325
|
+
|
326
|
+
def reverse(self):
|
327
|
+
"""
|
328
|
+
Returns the reverse of the ranking.
|
329
|
+
"""
|
330
|
+
r = Ranking({c: -r for c, r in self.rmap.items()}, cmap=self.cmap)
|
331
|
+
r.normalize_ranks()
|
332
|
+
return r
|
333
|
+
|
334
|
+
def break_tie(self, lin_order):
|
335
|
+
"""
|
336
|
+
Given a linear order, break the tie in the ranking by using the linear order. It is assumed that lin_order is a tuple of candidates such that are a tie according to the ranking. If not, then the function will return the same ranking.
|
337
|
+
"""
|
338
|
+
|
339
|
+
new_indiff_list = []
|
340
|
+
for cs in self.to_indiff_list():
|
341
|
+
if set(cs) == set(lin_order):
|
342
|
+
for c in lin_order:
|
343
|
+
new_indiff_list.append((c,))
|
344
|
+
else:
|
345
|
+
new_indiff_list.append(cs)
|
346
|
+
return Ranking.from_indiff_list(new_indiff_list, cmap=self.cmap)
|
347
|
+
|
348
|
+
def display(self, cmap = None):
|
349
|
+
"""
|
350
|
+
Display the ranking vertically as a column of a table.
|
351
|
+
|
352
|
+
:Example:
|
353
|
+
|
354
|
+
.. exec_code:: python
|
355
|
+
|
356
|
+
from pref_voting.profiles_with_ties import Ranking
|
357
|
+
r = Ranking({0:2, 1:1, 2:3})
|
358
|
+
print(r)
|
359
|
+
r.display()
|
360
|
+
print()
|
361
|
+
|
362
|
+
r = Ranking({0:1, 1:1, 2:3})
|
363
|
+
print(r)
|
364
|
+
r.display()
|
365
|
+
|
366
|
+
print()
|
367
|
+
r = Ranking({0:1, 2:3})
|
368
|
+
print(r)
|
369
|
+
r.display()
|
370
|
+
|
371
|
+
"""
|
372
|
+
cmap = cmap if cmap is not None else self.cmap
|
373
|
+
_r = copy.deepcopy(self)
|
374
|
+
_r.normalize_ranks()
|
375
|
+
print(
|
376
|
+
tabulate([[" ".join([
|
377
|
+
str(self.cmap[c])
|
378
|
+
for c in _r.cands_at_rank(rank)])]
|
379
|
+
for rank in _r.ranks],
|
380
|
+
tablefmt="pretty")
|
381
|
+
)
|
382
|
+
|
383
|
+
def __str__(self):
|
384
|
+
"""
|
385
|
+
Display the ranking as a string.
|
386
|
+
"""
|
387
|
+
r_str = ""
|
388
|
+
|
389
|
+
for r in self.ranks:
|
390
|
+
cands_at_rank = self.cands_at_rank(r)
|
391
|
+
if len(cands_at_rank) == 1:
|
392
|
+
r_str += str(self.cmap[cands_at_rank[0]]) + " "
|
393
|
+
else:
|
394
|
+
r_str += "( " + " ".join(map(lambda c: str(self.cmap[c]) + " ", cands_at_rank)) + ") "
|
395
|
+
return r_str
|
396
|
+
|
397
|
+
def __getitem__(self, r):
|
398
|
+
"""Returns the item at rank r + 1 if it is unique, otherwise return the list of items at rank r+1. Raises an exception if there is no item at rank r+1."""
|
399
|
+
|
400
|
+
normalized_ranks = {c: self.ranks.index(r) + 1 for c, r in self.rmap.items()}
|
401
|
+
|
402
|
+
ranks = sorted(list(set(normalized_ranks.values())))
|
403
|
+
|
404
|
+
assert r < len(ranks), "There is no item at rank " + str(r + 1)
|
405
|
+
cands_at_rank = [c for c,crank in normalized_ranks.items() if crank == ranks[r]]
|
406
|
+
|
407
|
+
return cands_at_rank[0] if len(cands_at_rank) == 1 else cands_at_rank
|
408
|
+
|
409
|
+
def __eq__(self, other):
|
410
|
+
|
411
|
+
"""
|
412
|
+
Returns True if the rankings are the same.
|
413
|
+
|
414
|
+
:Example:
|
415
|
+
|
416
|
+
.. exec_code:: python
|
417
|
+
|
418
|
+
from pref_voting.profiles_with_ties import Ranking
|
419
|
+
|
420
|
+
r = Ranking({1:2, 2:3})
|
421
|
+
r2 = Ranking({1:1, 2:2})
|
422
|
+
r3 = Ranking({1:1})
|
423
|
+
|
424
|
+
print(r == r2) # True
|
425
|
+
print(r == r3) # False
|
426
|
+
|
427
|
+
"""
|
428
|
+
|
429
|
+
self_ranks = self.ranks
|
430
|
+
other_ranks = other.ranks
|
431
|
+
|
432
|
+
if len(self_ranks) != len(other_ranks):
|
433
|
+
return False
|
434
|
+
|
435
|
+
for self_rank, other_rank in zip(self_ranks, other_ranks):
|
436
|
+
if set(self.cands_at_rank(self_rank)) != set(other.cands_at_rank(other_rank)):
|
437
|
+
return False
|
438
|
+
return True
|
439
|
+
|
440
|
+
def __hash__(self):
|
441
|
+
return hash(tuple(self.to_indiff_list()))
|
442
|
+
|
443
|
+
def break_ties_alphabetically(ranking):
|
444
|
+
"""Break ties in the ranking alphabetically.
|
445
|
+
|
446
|
+
Args:
|
447
|
+
ranking (Ranking): A ranking object
|
448
|
+
|
449
|
+
Returns:
|
450
|
+
A ranking object
|
451
|
+
"""
|
452
|
+
candidates = ranking.cands
|
453
|
+
|
454
|
+
new_ranking_dict = {}
|
455
|
+
|
456
|
+
n = 0
|
457
|
+
level = 0
|
458
|
+
|
459
|
+
while n < len(candidates):
|
460
|
+
sorted_cands_at_rank = ranking.cands_at_rank(level)
|
461
|
+
for c in sorted_cands_at_rank:
|
462
|
+
new_ranking_dict[c] = n
|
463
|
+
n += 1
|
464
|
+
level += 1
|
465
|
+
|
466
|
+
return Ranking(new_ranking_dict)
|