Source code for whalrus.ballots.ballot_levels

# -*- coding: utf-8 -*-
"""
Copyright Sylvain Bouveret, Yann Chevaleyre and François Durand
sylvain.bouveret@imag.fr, yann.chevaleyre@dauphine.fr, fradurand@gmail.com

This file is part of Whalrus.

Whalrus 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.

Whalrus 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 Whalrus.  If not, see <http://www.gnu.org/licenses/>.
"""
import numbers
from typing import KeysView, ValuesView, ItemsView
from whalrus.ballots.ballot_order import BallotOrder
from whalrus.scales.scale import Scale
from whalrus.scales.scale_range import ScaleRange
from whalrus.scales.scale_from_list import ScaleFromList
from whalrus.utils.utils import cached_property, dict_to_items, NiceSet, NiceDict, convert_number


[docs]class BallotLevels(BallotOrder): # noinspection PyRedeclaration """ Ballot with an evaluation of the candidates. Parameters ---------- b : dict Keys: candidates. Values represent some form of evaluation. The keys and the values must be hashable. candidates : set The candidates that were available at the moment when the voter cast her ballot. Default: candidates that are explicitly mentioned in the ballot :attr:`b`. scale : Scale The authorized scale of evaluations at the moment when the voter cast her ballot. Default: ``Scale()`` (meaning in this case "unknown"). Examples -------- Most general syntax: >>> ballot = BallotLevels({'a': 10, 'b': 7, 'c': 3}, ... candidates={'a', 'b', 'c', 'd', 'e'}, ... scale=ScaleRange(low=0, high=10)) Other examples of syntax: >>> ballot = BallotLevels({'a': 10, 'b': 7, 'c': 3}) >>> ballot = BallotLevels({'a': 'Good', 'b': 'Bad', 'c': 'Bad'}, ... scale=ScaleFromList(['Bad', 'Medium', 'Good'])) In addition to the set-like and list-like behaviors defined in parent class :class:`BallotOrder`, it also has a dictionary-like behavior in the sense that it implements ``__getitem__``: >>> ballot = BallotLevels({'a': 10, 'b': 7, 'c': 3}) >>> ballot['a'] 10 """ # Core features: ballot and candidates # ==================================== def __init__(self, b: dict, candidates: set=None, scale: Scale=None): if scale is None: scale = Scale() self.scale = scale super().__init__(b, candidates) def _parse(self, b: dict) -> None: """ For this subclass, the internal representation is of the form {'a': 10, 'b': 7, 'c': 3}, meaning that a has evaluation 10; b, 7; and c, 3. Parameters ---------- b : dict """ self._internal_representation = NiceDict({c: convert_number(v) for c, v in b.items()}) @cached_property def as_dict(self) -> NiceDict: """NiceDict: keys are candidates and values are levels of evaluation. Examples -------- >>> BallotLevels({'a': 10, 'b': 7, 'c': 3}).as_dict {'a': 10, 'b': 7, 'c': 3} """ return self._internal_representation @cached_property 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)] @cached_property def candidates_in_b(self) -> NiceSet: return NiceSet(self.as_dict.keys()) @cached_property def is_numeric(self): return all([isinstance(v, numbers.Number) for k, v in self.items()]) # Representation # ============== def __repr__(self) -> str: return 'BallotLevels(%s, candidates=%s, scale=%s)' % ( self.as_dict, self.candidates, repr(self.scale) ) def __str__(self) -> str: return ', '.join([str(k) + ': ' + str(v) for k, v in dict_to_items(self.as_dict)]) # Restrict the ballot # ===================
[docs] 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)
# Dictionary behavior # =================== def __getitem__(self, item: object) -> object: """ Get an evaluation. Parameters ---------- item : candidate Returns ------- object The evaluation for this candidate. Examples -------- >>> ballot = BallotLevels({'a': 10, 'b': 7, 'c': 3}) >>> ballot['a'] 10 """ return self.as_dict[item]
[docs] def keys(self) -> KeysView: """ Keys of the ballot. Returns ------- KeysView This is a shortcut for ``self.as_dict.keys()``. Examples -------- >>> ballot = BallotLevels({'a': 10, 'b': 7, 'c': 3}, candidates={'a', 'b', 'c', 'd', 'e'}) >>> sorted(ballot.keys()) ['a', 'b', 'c'] """ return self.as_dict.keys()
[docs] def values(self) -> ValuesView: """ Values of the ballot. Returns ------- ValuesView This is a shortcut for ``self.as_dict.values()``. Examples -------- >>> ballot = BallotLevels({'a': 10, 'b': 7, 'c': 3}, candidates={'a', 'b', 'c', 'd', 'e'}) >>> sorted(ballot.values()) [3, 7, 10] """ return self.as_dict.values()
[docs] def items(self) -> ItemsView: """ Items of the ballot. Returns ------- ItemsView This is a shortcut for ``self.as_dict.items()``. Examples -------- >>> ballot = BallotLevels({'a': 10, 'b': 7, 'c': 3}, candidates={'a', 'b', 'c', 'd', 'e'}) >>> sorted(ballot.items()) [('a', 10), ('b', 7), ('c', 3)] """ return self.as_dict.items()