Exemplo n.º 1
0
class StableMarriage(BaseGame):
    """A class for solving instances of the stable marriage problem (SM).

    Parameters
    ----------
    suitors : list of Player
        The suitors in the game. Each suitor must rank all elements in
        ``reviewers``.
    reviewers : list of Player
        The reviewers in the game. Each reviewer must rank all elements in
        ``suitors``.

    Attributes
    ----------
    matching : Matching or None
        Once the game is solved, a matching is available. This uses the suitors
        and reviewers as keys and values, respectively, in a ``Matching``
        object. Initialises as `None`.
    blocking_pairs : list of (Player, Player)
        The suitor-reviewer pairs that both prefer one another to their current
        match. Initialises as ``None``.
    """
    def __init__(self, suitors, reviewers):

        suitors, reviewers = copy.deepcopy([suitors, reviewers])
        self.suitors = suitors
        self.reviewers = reviewers

        super().__init__()
        self.check_inputs()

    @classmethod
    def create_from_dictionaries(cls, suitor_prefs, reviewer_prefs):
        """ Create an instance of SM from two preference dictionaries. """

        suitors, reviewers = _make_players(suitor_prefs, reviewer_prefs)
        game = cls(suitors, reviewers)

        return game

    def solve(self, optimal="suitor"):
        """Solve the instance of SM using either the suitor- or
        reviewer-oriented Gale-Shapley algorithm. Return the matching."""

        self.matching = Matching(
            stable_marriage(self.suitors, self.reviewers, optimal))
        return self.matching

    def check_validity(self):
        """ Check whether the current matching is valid. """

        unmatched_issues = self._check_for_unmatched_players()
        not_in_matching_issues = self._check_for_players_not_in_matching()
        inconsistency_issues = self._check_for_inconsistent_matches()

        if unmatched_issues or not_in_matching_issues or inconsistency_issues:
            raise MatchingError(
                unmatched_players=unmatched_issues,
                players_not_in_matching=not_in_matching_issues,
                inconsistent_matches=inconsistency_issues,
            )

        return True

    def check_stability(self):
        """Check for the existence of any blocking pairs in the current
        matching, thus determining the stability of the matching."""

        blocking_pairs = []
        for suitor in self.suitors:
            for reviewer in self.reviewers:
                if suitor.prefers(reviewer,
                                  suitor.matching) and reviewer.prefers(
                                      suitor, reviewer.matching):
                    blocking_pairs.append((suitor, reviewer))

        self.blocking_pairs = blocking_pairs
        return not any(blocking_pairs)

    def _check_for_unmatched_players(self):
        """ Check everyone has a match. """

        issues = []
        for player in self.suitors + self.reviewers:
            issue = player.check_if_match_is_unacceptable(unmatched_okay=False)
            if issue:
                issues.append(issue)

        return issues

    def _check_for_players_not_in_matching(self):
        """ Check that everyone appears in the matching. """

        players_in_matching = set(self.matching.keys()) | set(
            self.matching.values())

        issues = []
        for player in self.suitors + self.reviewers:
            if player not in players_in_matching:
                issues.append(f"{player} does not appear in matching.")

        return issues

    def _check_for_inconsistent_matches(self):
        """Check that the game matching is consistent with those of the
        players."""

        issues = []
        for suitor, reviewer in self.matching.items():
            if suitor.matching != reviewer:
                issues.append(
                    f"{suitor} is matched to {suitor.matching} but the "
                    f"matching says they should be matched to {reviewer}.")

        return issues

    def check_inputs(self):
        """Raise an error if any of the conditions of the game have been
        broken."""

        self._check_num_players()
        for suitor in self.suitors:
            self._check_player_ranks(suitor)
        for reviewer in self.reviewers:
            self._check_player_ranks(reviewer)

    def _check_num_players(self):
        """ Check that the number of suitors and reviewers are equal. """

        if len(self.suitors) != len(self.reviewers):
            raise ValueError(
                "There must be an equal number of suitors and reviewers.")

        return True

    def _check_player_ranks(self, player):
        """ Check that a player has ranked all of the other group. """

        others = self.reviewers if player in self.suitors else self.suitors
        if set(player.prefs) != set(others):
            raise ValueError(
                "Every player must rank each name from the other group. "
                f"{player}: {player.prefs} != {others}")

        return True
Exemplo n.º 2
0
def test_getitem():
    """ Check that you can access items in a Matching correctly. """

    matching = Matching(dictionary)
    for key, val in matching.items():
        assert matching[key] == val