from typing import List, Tuple class BikeDriveTrain: """ Class representing a bicycle drive train. Only a single cog on the front and single cog on the rear can be selected at one time. Note ---- Gear combination and ratio types are represented as a Tuple[int, int, float] with the integers representing front cog, and rear cog respectively, and the float representing the gear ratio. In the future, it would make sense for this to be a named tuple, alias of namedtuple, or even class of its own to be more explicit. """ def __init__(self, front_cogs: List[int], rear_cogs: List[int]): """ Parameters ---------- front_cogs: list[int] List of integers representing tooth counts for cogs on front crank. rear_cogs: list[int] List of integers representing tooth counts for cogs on rear cassette. """ #TODO Error handling for invalid inputs. Consider adding warnings for nonsensical gears/gear combinations # Assign respective cogs to instance. # ASSUMPTION: Gear sets won't need change after object creation, enforced as a read-only property. self._front_cogs = front_cogs self._rear_cogs = rear_cogs # Calculate the ratios for this drive train. # ASSUMPTION: Since front_cogs and rear_cogs won't change after instantiation, neither will the gear ratios. As # such, we only have to do this once, so it makes sense to do it at instantiation. # ASSUMPTION: Practically, real bicycles will have a small set of gearing combinations, so this method is # computationally trivial. gear_combinations_and_ratios = [] for front_cog in front_cogs: for rear_cog in rear_cogs: gear_combinations_and_ratios.append((front_cog, rear_cog, front_cog / rear_cog)) self._gear_combinations_and_ratios = gear_combinations_and_ratios # Getters for the cogs @property def front_cogs(self): """ list of int: List of integers representing tooth counts for cogs on front crank. """ return self._front_cogs @property def rear_cogs(self): """ list of int: List of integers representing tooth counts for cogs on rear cassette. """ return self._rear_cogs @property def gear_combinations_and_ratios(self) -> List[Tuple[int, int, float]]: """ list of tuple of (int, int, float): List of tuples describing the possible gear combinations and their respective gear ratio in the form (front_cog, rear_cog, gear ratio). """ # ASSUMPTION: This information will be useful to uses of the class. It is a convenience function for the # class itself return self._gear_combinations_and_ratios def get_gear_combination(self, target_ratio: float) -> Tuple[int, int, float]: """ Find the gear combination and its respective gear ratio that is nearest (but not over) target_ratio Parameters ---------- target_ratio A float representing the desired gear ratio. Returns ------- A tuple describing the gear combination and its respective gear ratio that is nearest (but not over) target_ratio in the form (front_cog, rear_cog, gear ratio). """ # first sort gear_combination_and_ratios from smallest to largest. candidate_gear_combinations_and_ratios = sorted(self.gear_combinations_and_ratios, key=lambda gear_combination_and_ratio: gear_combination_and_ratio[2]) # then eliminate gear ratios that are greater than the target. # #TODO error handling if all elements are eliminated candidate_gear_combinations_and_ratios = [(front_cog, rear_cog, gear_ratio) for (front_cog, rear_cog, gear_ratio) in candidate_gear_combinations_and_ratios if gear_ratio < target_ratio] # and the nearest without going over ratio is the last element of the candidate list target_gear_combination_and_ratio = candidate_gear_combinations_and_ratios[-1] return target_gear_combination_and_ratio pass def get_shift_sequence(self, target_ratio: float, initial_gear_combination: Tuple[int, int]) -> \ List[Tuple[int, int, float]]: """ A method that returns a shift sequence to traverse from an initial gear combination to a gear combination with the closest ratio that is less than or equal to the target ratio, following first shifting the front to the final gear, then shift the rear to the final gear. Parameters ---------- target_ratio A float representing the desired gear ratio. initial_gear_combination The starting gear combination in the form of (front_gear, rear_gear) where front_gear and rear_gear are integers describing the number of teeth in specified gear. Returns ------- List of tuple of int, int, float: Steps in gear shifting sequence in the form (front_cog, rear_cog, gear ratio) """ target_gear_combination_and_ratio = self.get_gear_combination(target_ratio) # TODO implement this method, using the rough steps below. # first determine if it is a down-shift or an up-shift. # sort shifting steps (large cog first, then small cog) depending on whether it's a down-shift or up-shift. # filter the list depending on target ratio, starting with the initial gear combination and stopping when the # closest ratio to the target is achieved. # return list # TODO In the meantime, this function will return a "not implemented" error. return self.gear_combinations_and_ratios def produce_formatted_shift_sequence(self, target_ratio: float, initial_gear_combination: Tuple[int, int]): """ A method to produce a formatted shift sequence for a given target ratio and initial gear combination. Parameters ---------- target_ratio A float representing the desired gear ratio. initial_gear_combination The starting gear combination in the form of (front_gear, rear_gear) where front_gear and rear_gear are integers describing the number of teeth in specified gear. Returns ------- None: Method only prints out the sequence to the console. """ # get the sequence using method. sequence = self.get_shift_sequence(target_ratio, initial_gear_combination) # print the sequence using string formatter for i, (front_gear, rear_gear, ratio) in enumerate(sequence): print(f"{i}: F:{front_gear}, R:{rear_gear}, {ratio:3f}")