def candidates(self, candidates): """ Validate a specification of candidates names Arguments --------- candidates An ordered collection of strings, each a unique candidate name, which meets the requirements for rcv.tabulate(). Returns ------- A tuple of the candidate names, in the same order, if they meet requirements. Raises ------ RcvValueError If the candidate names do not meet requirements. """ try: candidates = str_tuple(candidates) except TypeError as exc: raise errors.RcvValueError('Invalid candidates type:', (), exc) for ix, name in enumerate(candidates): if name in K.RANKING_CODES_NOT_A_CANDIDATE or name[0] == ':': raise errors.RcvValueError('Invalid candidate name:', ( ('candidate name', name), ('candidate name index', ix), )) if len(set(candidates)) != len(candidates): raise errors.RcvValueError('Candidate names are not unique.') return candidates
def max_ranking_levels(self, max_ranking_levels): """Validate the maximum number of candidates that can be ranked Arguments --------- max_ranking_levels Must be None or an int that is at least three. Returns ------- max_ranking_levels if it meets requirements. Raises ------ RcvValueError If max_ranking_levels does not meet requirements. """ if max_ranking_levels is None: return max_ranking_levels if type(max_ranking_levels) != int: raise errors.RcvValueError('max_ranking_levels not an int:', ( ('type(max_ranking_levels)', type(max_ranking_levels)), )) if max_ranking_levels < K.MIN_RANKINGS_SUPPORTED: raise errors.RcvValueError('max_ranking_levels is less than {}:'. format(K.MIN_RANKINGS_SUPPORTED), ( ('max_ranking_levels', max_ranking_levels), )) return max_ranking_levels
def nbr_seats_to_fill(self, nbr_seats_to_fill): """Validate the number of seats to fill Arguments --------- nbr_seats_to_fill Must be a positive int Returns ------- nbr_seats_to_fill if it meets requirements. Raises ------ RcvValueError If nbr_seats_to_fill does not meet requirements. """ if type(nbr_seats_to_fill) != int: raise errors.RcvValueError('nbr_seats_to_fill not an int:', ( ('type(nbr_seats_to_fill)', type(nbr_seats_to_fill)), )) if nbr_seats_to_fill <= 0: raise errors.RcvValueError('nbr_seats_to_fill not >= 1:', ( ('nbr_seats_to_fill', nbr_seats_to_fill), )) return nbr_seats_to_fill
def get_alt_defeats_option(self): """ Get the value for alternative defeats for the current round Returns ------- A string that is the option for alternative defeats in the round Raises ------ RcvValueError If the option is stored as a tuple of strings, one per round, but the tuple is too short. """ options_value = self.options[K.OPTION_ALTERNATIVE_DEFEATS] if type(options_value) == str: alt_defeats_option = options_value else: try: alt_defeats_option = options_value[self.nbr_round - 1] except IndexError: raise errors.RcvValueError( 'Alternative defeats option tuple too short.', ( ('nbr_round', nbr_round), ('len(options_value)', len(options_value)), )) return alt_defeats_option
def resolve_tie(self, tied_candidates): """ Select the tied candidate that is earliest in the tie_breaker. Arguments --------- tied_candidates A set of one or more candidates that are tied. Returns ------- The candidate name with the lowest tie_breaker index. Raises ------ RcvValueError If there is a tied candidate not in self.tie_breaker. """ if len(tied_candidates) <= 1: return list(tied_candidates)[0] not_in_tie_breaker = tied_candidates.difference(set(self.tie_breaker)) if not_in_tie_breaker: raise errors.RcvValueError('Tied candidate not in tie_breaker:', ( ('candidate', not_in_tie_breaker.pop()), ('round', self.nbr_round), ('tied_candidates', tied_candidates), ('tie_breaker', self.tie_breaker), )) tied_candidates_by_index = {index: candidate for candidate, index in self.tie_breaker.items() if candidate in tied_candidates} selected_candidate = tied_candidates_by_index[ min(tied_candidates_by_index)] return selected_candidate
def tie_breaker(self, tie_breaker, candidates): """ Validate and convert a specification of a tie_breaker Arguments --------- tie_breaker An ordered collection of strings which meets the rcv.tabulate requirements for a tie_breaker. candidates A tuple of the names of all candidates. Returns ------- A tie_breaker as a dictionary keyed by candidate names with the ordering indexes as values, if the tie_breaker argument meets requirements. Raises ------ RcvValueError If the tie_breaker argument does not meet requirements. """ try: tie_breaker = str_tuple(tie_breaker) except TypeError as exc: raise errors.RcvValueError('Invalid tie_breaker type:', (), exc) for ix, name in enumerate(tie_breaker): if name not in candidates: raise errors.RcvValueError('Invalid candidate name in tie_breaker:', ( ('candidate name', name), ('tie_breaker index', ix), )) result = {candidate: index for index, candidate in enumerate(tie_breaker)} if len(result) != len(tie_breaker): raise errors.RcvValueError( 'Candidate names in tie_breaker are not unique.') return result
def options(self, options): """Validate a dictionary of rcv.tabulate options Arguments --------- options A dictionary of options that are valid for the the rcv.tabulate function. An option is valid even if it might not be used. Returns ------- An options dictionary, if the options argument meets requirements. Raises ------ RcvValueError If options does not meet requirements. """ result = {} if type(options) != dict: raise errors.RcvValueError('options is not a dict:', ( ('type(options)', type(options)), )) for name, value in options.items(): if type(name) != str: raise errors.RcvValueError('An option name is not a str:', ( ('option name', name), )) if name == K.OPTION_STOP_AT_MAJORITY: if value is not True and value is not False: raise errors.RcvValueError('The option {} must be True or False.'. format(repr(K.OPTION_STOP_AT_MAJORITY))) result[K.OPTION_STOP_AT_MAJORITY] = value elif name == K.OPTION_ALTERNATIVE_DEFEATS: if (type(value) == str and value.upper() in K.OPTION_ALTERNATIVE_DEFEATS_VALUE_SET): value = value.upper() else: try: value = str_tuple(value) except TypeError as exc: raise errors.RcvValueError( 'Invalid option value type:', ( ('option name', K.OPTION_ALTERNATIVE_DEFEATS), ), exc) for ix, per_round_value in enumerate(value): if (per_round_value.upper() not in K.OPTION_ALTERNATIVE_DEFEATS_VALUE_SET): raise errors.RcvValueError('Invalid per-round option value:', ( ('per-round value', per_round_value), ('index', ix), ('for round', ix + 1), ('option name', K.OPTION_ALTERNATIVE_DEFEATS), )) value = tuple([per_round_value.upper() for per_round_value in value]) result[K.OPTION_ALTERNATIVE_DEFEATS] = value else: raise errors.RcvValueError('Invalid option name:', ( ('option name', name), )) return result
def ballots(self, ballots, candidates, max_ranking_levels): """ Validate a specification of ballots Arguments --------- ballots A valid specification of ballots that meet the requirements of the rcv.tabulate function. candidates A tuple of all the names of all candidates. max_ranking_levels The maximum length of a ballot's rankings, possibly None. Returns ------- A tuple of the ballots, each converted to a Ballot object, in the same order as ballots, if ballots meets requirements. Raises ------ RcvValueError If the ballots do not meet requirements. """ result = [] if type(ballots) not in (list, tuple): raise errors.RcvValueError('ballots is not a list or tuple:', ( ('type(ballots)', type(ballots)), )) for ix, ballot in enumerate(ballots): if type(ballot) not in (list, tuple): raise errors.RcvValueError('A ballot is not a list or tuple:', ( ('type(ballot)', type(ballot)), ('ballot index', ix), )) if len(ballot) != 2: raise errors.RcvValueError('A ballot is not a pair of values:', ( ('len(ballot)', len(ballot)), ('ballot index', ix), )) multiple = ballot[0] if type(multiple) != int: raise errors.RcvValueError('A ballot multiple is not an int:', ( ('type(multiple)', type(multiple)), ('ballot index', ix), )) if multiple < 1: raise errors.RcvValueError('A ballot multiple is zero or less:', ( ('multiple', multiple), ('ballot index', ix), )) try: rankings = str_tuple(ballot[1]) except TypeError as exc: raise errors.RcvValueError('Invalid ballot rankings type:', ( ('ballot index', ix), ), exc) if (max_ranking_levels is not None and len(rankings) > max_ranking_levels): raise errors.RcvValueError('Ballot rankings is too long:', ( ('len(rankings)', len(rankings)), ('max_ranking_levels', max_ranking_levels), ('ballot index', ix), )) for rix, ranking_code in enumerate(rankings): if (ranking_code not in candidates and ranking_code not in K.RANKING_CODES_NOT_A_CANDIDATE): raise errors.RcvValueError('Invalid ballot ranking code:', ( ('ranking code', ranking_code), ('ballot index', ix), ('ranking code index', rix), )) internal_ballot = Ballot(multiple, rankings) result.append(internal_ballot) result = tuple(result) return result