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: 688605907
Change-Id: I7595dc9fcad5acd5b12e81cfd4ea48e0517c474d
  • Loading branch information
Anna Koop authored and lanctot committed Oct 22, 2024
1 parent 1560a35 commit 2228e1c
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 41 deletions.
61 changes: 33 additions & 28 deletions open_spiel/python/voting/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@
"""Base classes for voting methods."""

import abc
from typing import Dict, List, NamedTuple, Tuple, Union
from collections.abc import Sequence
from typing import NamedTuple, TypeAlias

import numpy as np


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

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


# Basic type to represent a vote.
Expand All @@ -34,21 +36,23 @@ 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: Union[List[PreferenceList], List[WeightedVote], None] = None,
alternatives: Union[List[AlternativeId], None] = None,
votes: Sequence[VoteType] | None = None,
alternatives: Sequence[AlternativeId] | None = None
):
"""Initialize the preference profile.
Expand All @@ -71,13 +75,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 @@ -109,7 +113,7 @@ def _register_alternatives_from_votes(self):
self._register_alternative(alternative)

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

def add_vote_from_values(
self,
values: Union[List[float], List[int]],
values: Sequence[float],
tie_tolerance: float = 1e-10,
weight: int = 1,
):
Expand Down Expand Up @@ -189,17 +193,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 @@ -244,7 +248,7 @@ def margin_matrix(self) -> np.ndarray:
return pref_matrix - pref_matrix.T

def condorcet_winner(
self, strong: bool = True, margin_matrix: Union[np.ndarray, None] = None
self, strong: bool = True, margin_matrix: np.ndarray | None = None
):
"""Returns the Condorcet winner(s).
Expand Down Expand Up @@ -336,14 +340,15 @@ 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: List[Tuple[AlternativeId, float]]
self,
ranked_alternatives_and_scores: Sequence[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 @@ -352,16 +357,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 @@ -389,7 +394,7 @@ def __str__(self) -> str:
str_rep += "Scores: " + str(self._scores)
return str_rep

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

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

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


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

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

def __init__(
self, num_winners: Union[int, None] = None, verbose: bool = False
self, num_winners: int | None = None, verbose: bool = False
):
"""Construct an instance of STV with the specified number of winners.
Expand All @@ -59,17 +58,17 @@ def name(self) -> str:
def _is_still_active(
self,
alternative: base.AlternativeId,
winners: List[base.AlternativeId],
losers: List[base.AlternativeId],
winners: Sequence[base.AlternativeId],
losers: Sequence[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: List[base.AlternativeId],
losers: List[base.AlternativeId],
winners: Sequence[base.AlternativeId],
losers: Sequence[base.AlternativeId],
) -> int:
""""Returns the next index in the list that is still in the running."""
new_idx = mutable_vote.idx + 1
Expand All @@ -82,9 +81,9 @@ def _next_idx_in_the_running(
def _initial_scores_for_round(
self,
profile: base.PreferenceProfile,
winners: List[base.AlternativeId],
losers: List[base.AlternativeId],
) -> Dict[base.AlternativeId, float]:
winners: Sequence[base.AlternativeId],
losers: Sequence[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 @@ -96,7 +95,7 @@ def _remove_winning_votes(
self,
winning_alt: base.AlternativeId,
num_to_remove: int,
all_votes: List[MutableVote],
all_votes: Sequence[MutableVote],
):
while num_to_remove > 0:
for mutable_vote in all_votes:
Expand Down Expand Up @@ -129,7 +128,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 2228e1c

Please sign in to comment.