# -*- 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/>.
"""
from functools import cmp_to_key
from typing import Iterable
[docs]class Scale:
"""
A scale used to evaluate the candidates (for :class:`RuleRangeVoting`, :class:`RuleMajorityJudgment`, etc).
This parent class represents a generic scale, where two levels of the scale compare according to their internal
methods ``__lt__``, ``__le__``, etc.
For a subclass, it is sufficient to override the method :meth:`lt` and the other comparison methods will be
modified accordingly (assuming it describes a total order).
Examples
--------
>>> scale = Scale()
>>> scale.lt(1, 7)
True
"""
# noinspection PyMethodMayBeStatic
[docs] def eq(self, one: object, another: object) -> bool:
"""
Test "equal". Cf. :meth:`lt`.
"""
return one == another
# noinspection PyMethodMayBeStatic
[docs] def ne(self, one: object, another: object) -> bool:
"""
Test "not equal". Cf. :meth:`lt`.
"""
return not self.eq(one, another)
# noinspection PyMethodMayBeStatic
[docs] def lt(self, one: object, another: object) -> bool:
"""
Test "lower than".
Generally, only this method is overridden in the subclasses.
Parameters
----------
one : object
A level of the scale.
another : object
A level of the scale.
Returns
-------
bool
True iff :attr:`one` is lower than :attr:`another`.
Examples
--------
>>> Scale().lt('a', 'z')
True
"""
return one < another
# noinspection PyMethodMayBeStatic
[docs] def le(self, one: object, another: object) -> bool:
"""
Test "lower or equal". Cf. :meth:`lt`.
"""
return self.eq(one, another) or self.lt(one, another)
# noinspection PyMethodMayBeStatic
[docs] def gt(self, one: object, another: object) -> bool:
"""
Test "greater than". Cf. :meth:`lt`.
"""
return self.lt(another, one)
# noinspection PyMethodMayBeStatic
[docs] def ge(self, one: object, another: object) -> bool:
"""
Test "greater or equal". Cf. :meth:`lt`.
"""
return self.le(another, one)
@property
def low(self):
"""object: The lowest element of the scale (or None if the scale is unbounded below).
"""
return None
@property
def high(self):
"""object: The highest element of the scale (or None if the scale is unbounded above).
"""
return None
@property
def is_bounded(self):
return (self.low is not None) and (self.high is not None)
def __repr__(self):
return '%s()' % type(self).__name__
# Min, max and sort
# -----------------
[docs] def compare(self, one: object, another: object) -> int:
"""
Compare two levels.
Parameters
----------
one : object
A level.
another : object
A level.
Returns
-------
int
0 if they are equal, a positive number if ``one`` is greater than ``another``, a negative number otherwise.
Examples
--------
>>> Scale().compare('a', 'z')
-1
"""
if self.eq(one, another):
return 0
return -1 if self.lt(one, another) else 1
[docs] def min(self, iterable: Iterable) -> object:
"""
Minimum of some levels.
Parameters
----------
iterable : Iterable
An iterable of levels (list, set, etc).
Examples
--------
>>> Scale().min({'x', 'a', 'z'})
'a'
"""
return min(iterable, key=cmp_to_key(self.compare))
[docs] def max(self, iterable: Iterable) -> object:
"""
Maximum of some levels.
Parameters
----------
iterable : Iterable
An iterable of levels (list, set, etc).
Examples
--------
>>> Scale().max({4, 1, 12})
12
"""
return max(iterable, key=cmp_to_key(self.compare))
[docs] def sort(self, some_list: list, reverse: bool = False) -> None:
"""
Sort a list of levels (in place).
Parameters
----------
some_list : list
A list of levels.
reverse : bool
If True, then sort in decreasing order.
Examples
--------
>>> some_list = [42, 3, 12]
>>> Scale().sort(some_list)
>>> some_list
[3, 12, 42]
"""
some_list.sort(key=cmp_to_key(self.compare), reverse=reverse)
[docs] def argsort(self, some_list: list, reverse: bool = False) -> list:
"""
"Argsort" a list of levels.
Parameters
----------
some_list : list
A list of levels.
reverse : bool
If True, then argsort in decreasing order.
Returns
-------
list
A list of indexes.
Examples
--------
>>> Scale().argsort(['a', 'c', 'b'])
[0, 2, 1]
"""
def compare_indexes(one: int, another: int) -> int:
return self.compare(some_list[one], some_list[another])
return sorted(range(len(some_list)), key=cmp_to_key(compare_indexes), reverse=reverse)