def _parse(self, b: object) -> None: """ Assign `self._internal_representation`. The form of `self._internal_representation` may depend on the subclass. For the mother class `BallotOrder`, it is of the form [{'a', 'b'}, {'c'}], meaning a ~ b > c. It is used directly for self.as_weak_order. Parameters ---------- b : object The ballot in a loose input format (cf. documentation of the class and unit tests). """ if isinstance(b, tuple): b = list(b) if isinstance(b, list): self._internal_representation = [ NiceSet(s) if isinstance(s, set) else NiceSet({s}) for s in b ] elif isinstance(b, dict): self._internal_representation = [ NiceSet({k for k in b.keys() if b[k] == v}) for v in sorted(set(b.values()), reverse=True) ] elif isinstance(b, str): self._internal_representation = parse_weak_order(b) else: raise TypeError('Cannot interpret as an order: %r.' % b)
def candidates(self) -> NiceSet: if self._input_candidates is None: if self.candidate is None: logging.debug( 'The list of candidates was not explicitly given. Using the empty set instead.' ) return NiceSet() else: logging.debug( 'The list of candidates was not explicitly given. Using singleton {%s} instead.' % self.candidate) return NiceSet({self.candidate}) return NiceSet(self._input_candidates)
def eliminated_(self) -> NiceSet: """NiceSet: The eliminated candidates. This should always be non-empty. It may contain all the candidates (for example, it is always the case when there was only one candidate in the election). """ return NiceSet(c for tie_class in self.eliminated_order_ for c in tie_class)
def candidates_in_b(self) -> NiceSet: """NiceSet: The candidate that is explicitly mentioned in the ballot. This is a singleton with the only candidate contained in the ballot (or an empty set in case of abstention). Examples -------- >>> BallotOneName('a', candidates={'a', 'b', 'c'}).candidates_in_b {'a'} >>> BallotOneName(None, candidates={'a', 'b', 'c'}).candidates_in_b {} """ if self.candidate is None: return NiceSet() else: return NiceSet({self.candidate})
def candidates_not_in_b(self) -> NiceSet: """NiceSet: The candidates that were available at the moment of the vote, but are not explicitly mentioned in the ballot. Examples -------- >>> BallotOneName('a', candidates={'a', 'b', 'c'}).candidates_not_in_b {'b', 'c'} """ return NiceSet(self.candidates - {self.candidate})
def candidates_not_in_b(self) -> NiceSet: """NiceSet: the candidates that were available at the moment of the vote, but are not explicitly mentioned in the ballot. Examples -------- >>> BallotOrder('a ~ b > c', candidates={'a', 'b', 'c', 'd', 'e'}).candidates_not_in_b {'d', 'e'} """ return NiceSet(self.candidates - self.candidates_in_b)
def candidates_in_b(self) -> NiceSet: """NiceSet: the candidates that are explicitly mentioned in the ballot. Examples -------- >>> BallotOrder('a ~ b > c', candidates={'a', 'b', 'c', 'd', 'e'}).candidates_in_b {'a', 'b', 'c'} """ return NiceSet(c for indifference_class in self.as_weak_order for c in indifference_class)
def order_(self) -> list: """list: Result of the election as a (weak) order over the candidates. It is a list of :class:`NiceSet`. The first set contains the candidates that have the best score, the second set contains those with the second best score, etc. """ return [ NiceSet(k for k in self.scores_.keys() if self.scores_[k] == v) for v in sorted(set(self.scores_.values()), key=cmp_to_key(self.compare_scores), reverse=True) ]
def __call__(self, ballots: Union[list, Profile] = None, weights: list = None, voters: list = None, candidates: set = None): self.profile_original_ = Profile(ballots, weights=weights, voters=voters) self.profile_converted_ = Profile([self.converter(b, candidates) for b in self.profile_original_], weights=self.profile_original_.weights, voters=self.profile_original_.voters) if candidates is None: candidates = set().union(*[b.candidates for b in self.profile_converted_]) self.candidates_ = NiceSet(candidates) self._check_profile(candidates) self.delete_cache() return self
def restrict(self, candidates: set = None, **kwargs) -> 'BallotLevels': if kwargs: raise TypeError( "restrict() got an unexpected keyword argument %r" % list(kwargs.keys())[0]) if candidates is None: return self return BallotLevels( {k: v for k, v in self.as_dict.items() if k in candidates}, candidates=NiceSet(self.candidates & candidates), scale=self.scale)
def order_(self) -> list: orders = [rule.order_ for rule in self.rules_] # rank_tuples[a] will be (rank in order 0, rank in order 1, ...) rank_tuples = {c: [] for c in self.candidates_} for order in orders: for i, tie_class in enumerate(order): for c in tie_class: rank_tuples[c].append(i) rank_tuples = {k: tuple(v) for k, v in rank_tuples.items()} # Now, sort by lexicographic order of "rank tuples" return [NiceSet(k for k in rank_tuples.keys() if rank_tuples[k] == v) for v in sorted(set(rank_tuples.values()))]
def restrict(self, candidates: set = None, **kwargs) -> 'BallotOneName': """ Restrict the ballot to less candidates. Parameters ---------- candidates : set of candidates It can be any set of candidates, not necessarily a subset of ``self.candidates``). Default: ``self.candidates``. kwargs * `priority`: a :class:`Priority`. Default: :attr:`Priority.UNAMBIGUOUS`. Returns ------- BallotOneName The same ballot, "restricted" to the candidates given. Examples -------- >>> BallotOneName('a', candidates={'a', 'b'}).restrict(candidates={'b'}) BallotOneName('b', candidates={'b'}) >>> BallotOneName('a', candidates={'a', 'b', 'c'}).restrict(candidates={'b', 'c'}, ... priority=Priority.ASCENDING) BallotOneName('b', candidates={'b', 'c'}) """ # noinspection PyUnresolvedReferences priority = kwargs.pop('priority', Priority.UNAMBIGUOUS) if kwargs: raise TypeError( "restrict() got an unexpected keyword argument %r" % list(kwargs.keys())[0]) if candidates is None: return self if self.candidate in candidates: return self.__class__(self.candidate, NiceSet(self.candidates & candidates)) return self._restrict(restricted_candidates=NiceSet(self.candidates & candidates), priority=priority)
def candidates(self) -> NiceSet: """NiceSet: the candidates. If the set was not explicitly given, the candidates are inferred from the ballot. Examples -------- >>> BallotOrder('a ~ b > c', candidates={'a', 'b', 'c', 'd', 'e'}).candidates {'a', 'b', 'c', 'd', 'e'} >>> BallotOrder('a ~ b > c').candidates {'a', 'b', 'c'} """ if self._input_candidates is None: return self.candidates_in_b return NiceSet(self._input_candidates)
def eliminated_order_(self): if self.k > 0: n_wanted = self.k else: n_wanted = self.rule_.n_candidates_ + self.k if n_wanted <= 0 or n_wanted >= self.rule_.n_candidates_: return self.rule_.order_ worst_first = [] for tie_class in self.rule_.order_[::-1]: size_class = len(tie_class) if size_class <= n_wanted: worst_first.append(tie_class) n_wanted -= size_class if n_wanted == 0: break else: worst_first.append( NiceSet( self.rule_.tie_break.sort(tie_class)[-1:-1 - n_wanted:-1])) break return worst_first[::-1]
def candidates_in_b(self) -> NiceSet: return NiceSet(self.as_dict.keys())
def as_weak_order(self) -> list: return [ NiceSet(k for k in self.as_dict.keys() if self.as_dict[k] == v) for v in sorted(set(self.as_dict.values()), reverse=True) ]
def qualified_(self) -> NiceSet: """NiceSet: The candidates that are qualified (not eliminated). """ return NiceSet(self.rule_.candidates_ - self.eliminated_)
def order_(self) -> list: matrix = self.matrix_majority_ condorcet_winners = {c for c in matrix.candidates_ if min({v for (i, j), v in matrix.as_dict_.items() if i == c and j != c}) == 1} other_candidates = self.candidates_ - condorcet_winners return [NiceSet(tie_class) for tie_class in [condorcet_winners, other_candidates] if tie_class]
def order_(self) -> list: return [ NiceSet(k for k in self.scores_.keys() if self.scores_[k] == v) for v in sorted(set(self.scores_.values()), reverse=True) ]
def cotrailers_(self): """NiceSet: "Cotrailers". The set of candidates with the worst score. """ return NiceSet( {k for k, v in self.scores_.items() if v == self.worst_score_})
def cowinners_(self): """NiceSet: Cowinners. The set of candidates with the best score. """ return NiceSet( {k for k, v in self.scores_.items() if v == self.best_score_})