Skip to content

Commit

Permalink
As of Python 3.9, [collections.abc](https://docs.python.org/3/library…
Browse files Browse the repository at this point in the history
…/collections.abc.html#collections-abstract-base-classes) is recommended for type statements.

Converting `List` types to more generic `Sequence` where appropriate, and
replacing explicit `Type.List` references with primitive `list`.

PiperOrigin-RevId: 699222648
Change-Id: I6d1693c3cd2280ec508ecc9103ba51be48733191
  • Loading branch information
lanctot committed Nov 22, 2024
1 parent 8674474 commit 87a59c4
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 45 deletions.
61 changes: 28 additions & 33 deletions open_spiel/python/voting/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,15 @@
"""Base classes for voting methods."""

import abc
from collections.abc import Sequence
from typing import NamedTuple, TypeAlias

from typing import Dict, List, NamedTuple, Tuple, Union
import numpy as np


# The id of an alternative can be a string or an integer.
AlternativeId = str | int
AlternativeId = Union[str, int]

# list of alternative ids.
PreferenceList = Sequence[AlternativeId]
# List of alternative ids.
PreferenceList = List[AlternativeId]


# Basic type to represent a vote.
Expand All @@ -36,23 +34,21 @@ class WeightedVote(NamedTuple):
weight: int
vote: PreferenceList

VoteType: TypeAlias = PreferenceList | WeightedVote


class PreferenceProfile(object):
"""Base class for preference profiles.
IMPORTANT NOTE: see the assumptions below about indexing of alternatives.
"""
_votes: list[WeightedVote] # Tracks cast votes along with their count
_alternatives_dict: dict[AlternativeId, int] # Maps ID to index
_votes: List[WeightedVote] # Tracks cast votes along with their count
_alternatives_dict: Dict[AlternativeId, int] # Maps ID to index
# Identifiers for all possible alternatives
_alternatives_ids: list[AlternativeId]
_alternatives_ids: List[AlternativeId]

def __init__(
self,
votes: Sequence[VoteType] | None = None,
alternatives: Sequence[AlternativeId] | None = None
votes: Union[List[PreferenceList], List[WeightedVote], None] = None,
alternatives: Union[List[AlternativeId], None] = None,
):
"""Initialize the preference profile.
Expand All @@ -75,13 +71,13 @@ def __init__(
The alternatives_dict property below will return a dictionary of alternative
IDs to index.
"""
# list of Vote named tuples from above.
self._votes: list[WeightedVote] = []
# List of Vote named tuples from above.
self._votes: List[WeightedVote] = []
# alternative id -> index (used for registering alternatives)
self._alternatives_dict: dict[AlternativeId, int] = {}
self._alternatives_dict: Dict[AlternativeId, int] = {}
# IDs (labels) of each alternative (usually strings). The alternative's
# index is then the index of this array.
self._alternatives_ids: list[AlternativeId] = []
self._alternatives_ids: List[AlternativeId] = []

# Register the alternatives and add the votes, if any are provided.
if alternatives is not None:
Expand Down Expand Up @@ -113,7 +109,7 @@ def _register_alternatives_from_votes(self):
self._register_alternative(alternative)

def add_vote(
self, vote: VoteType, weight: int = 1
self, vote: Union[PreferenceList, WeightedVote], weight: int = 1
):
"""Add a vote to this preference profile.
Expand Down Expand Up @@ -141,7 +137,7 @@ def add_vote(

def add_vote_from_values(
self,
values: Sequence[float],
values: Union[List[float], List[int]],
tie_tolerance: float = 1e-10,
weight: int = 1,
):
Expand Down Expand Up @@ -193,17 +189,17 @@ def add_vote_from_values(
self.add_vote(named_vote, weight=weight)

@property
def votes(self) -> list[WeightedVote]:
def votes(self) -> List[WeightedVote]:
"""Returns a list of votes."""
return self._votes

@property
def alternatives(self) -> list[AlternativeId]:
def alternatives(self) -> List[AlternativeId]:
"""Returns a list of alternatives."""
return self._alternatives_ids

@property
def alternatives_dict(self) -> dict[AlternativeId, int]:
def alternatives_dict(self) -> Dict[AlternativeId, int]:
"""Returns a dict of alternative id -> index for each alternative."""
return self._alternatives_dict

Expand Down Expand Up @@ -248,7 +244,7 @@ def margin_matrix(self) -> np.ndarray:
return pref_matrix - pref_matrix.T

def condorcet_winner(
self, strong: bool = True, margin_matrix: np.ndarray | None = None
self, strong: bool = True, margin_matrix: Union[np.ndarray, None] = None
):
"""Returns the Condorcet winner(s).
Expand Down Expand Up @@ -340,15 +336,14 @@ class RankOutcome(object):
"""Basic object for outcomes of the voting methods."""

def __init__(self, rankings=None, scores=None):
self._rankings: list[AlternativeId] = rankings
self._scores: list[float] = scores
self._rank_dict: dict[AlternativeId, int] = None
self._rankings: List[AlternativeId] = rankings
self._scores: List[float] = scores
self._rank_dict: Dict[AlternativeId, int] = None
if self._rankings is not None:
self.make_rank_dict()

def unpack_from(
self,
ranked_alternatives_and_scores: Sequence[tuple[AlternativeId, float]],
self, ranked_alternatives_and_scores: List[Tuple[AlternativeId, float]]
):
"""A rank outcome that comes packed as (alternative id, score) tuples."""
self._rankings, self._scores = zip(*ranked_alternatives_and_scores)
Expand All @@ -357,16 +352,16 @@ def unpack_from(
self.make_rank_dict()

@property
def ranking(self) -> list[AlternativeId]:
def ranking(self) -> List[AlternativeId]:
"""Returns an ordered list W of alternatives' ids (winner is first)."""
return self._rankings

@property
def scores(self) -> list[float]:
def scores(self) -> List[float]:
"""Returns a alternative's scores S (in the same order as the ranking)."""
return self._scores

def ranking_with_scores(self) -> tuple[list[AlternativeId], list[float]]:
def ranking_with_scores(self) -> Tuple[List[AlternativeId], List[float]]:
"""Returns an ordered list of alternative ids and dict of scores W, S."""
return self._rankings, self._scores

Expand Down Expand Up @@ -394,7 +389,7 @@ def __str__(self) -> str:
str_rep += "Scores: " + str(self._scores)
return str_rep

def pretty_table_string(self, top: int | None = None):
def pretty_table_string(self, top: Union[int, None] = None):
"""Return an easier-to-read table for the rankings and scores.
Args:
Expand Down Expand Up @@ -426,7 +421,7 @@ def pretty_table_string(self, top: int | None = None):
return table_string

def pretty_latex_table(
self, header: str | None = None, top: int | None = None
self, header: Union[str, None] = None, top: Union[int, None] = None
):
"""Return an easier-to-read table string for the rankings and scores.
Expand Down
25 changes: 13 additions & 12 deletions open_spiel/python/voting/stv.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
Based on https://en.wikipedia.org/wiki/Single_transferable_vote.
"""
from collections.abc import Sequence

from typing import Dict, List, Union
from open_spiel.python.voting import base


Expand All @@ -30,7 +31,7 @@ class MutableVote(object):
alternative.
"""

def __init__(self, idx: int, weight: int, vote: Sequence[base.AlternativeId]):
def __init__(self, idx: int, weight: int, vote: List[base.AlternativeId]):
self.idx = idx
self.weight = weight
self.vote = vote
Expand All @@ -40,7 +41,7 @@ class STVVoting(base.AbstractVotingMethod):
"""Implements STV method."""

def __init__(
self, num_winners: int | None = None, verbose: bool = False
self, num_winners: Union[int, None] = None, verbose: bool = False
):
"""Construct an instance of STV with the specified number of winners.
Expand All @@ -58,17 +59,17 @@ def name(self) -> str:
def _is_still_active(
self,
alternative: base.AlternativeId,
winners: Sequence[base.AlternativeId],
losers: Sequence[base.AlternativeId],
winners: List[base.AlternativeId],
losers: List[base.AlternativeId],
) -> bool:
"""Returns whether the alternative is still in the running."""
return alternative not in winners and alternative not in losers

def _next_idx_in_the_running(
self,
mutable_vote: MutableVote,
winners: Sequence[base.AlternativeId],
losers: Sequence[base.AlternativeId],
winners: List[base.AlternativeId],
losers: List[base.AlternativeId],
) -> int:
""""Returns the next index in the list that is still in the running."""
new_idx = mutable_vote.idx + 1
Expand All @@ -81,9 +82,9 @@ def _next_idx_in_the_running(
def _initial_scores_for_round(
self,
profile: base.PreferenceProfile,
winners: Sequence[base.AlternativeId],
losers: Sequence[base.AlternativeId],
) -> dict[base.AlternativeId, float]:
winners: List[base.AlternativeId],
losers: List[base.AlternativeId],
) -> Dict[base.AlternativeId, float]:
"""Returns round's initial scores for alternatives still in the running."""
alt_scores = {}
for alt in profile.alternatives:
Expand All @@ -95,7 +96,7 @@ def _remove_winning_votes(
self,
winning_alt: base.AlternativeId,
num_to_remove: int,
all_votes: Sequence[MutableVote],
all_votes: List[MutableVote],
):
while num_to_remove > 0:
for mutable_vote in all_votes:
Expand Down Expand Up @@ -128,7 +129,7 @@ def run_election(self, profile: base.PreferenceProfile) -> base.RankOutcome:
# the current alternative that this vote is representing. They all start at
# 0 at the start, corresponding to their highest preference, and they get
# incremented as they become used up.
all_votes: list[MutableVote] = []
all_votes: List[MutableVote] = []
for vote in votes:
all_votes.append(MutableVote(idx=0, weight=vote.weight, vote=vote.vote))
while len(winners) + len(losers) < m:
Expand Down

0 comments on commit 87a59c4

Please sign in to comment.