CMU Coding Bootcamp
at main 6.4 kB view raw
1from typing import List, Optional 2 3 4class Odometer: 5 """An odometer where all digits must be in ascending order""" 6 7 miles: int 8 9 @staticmethod 10 def _is_error(miles: int) -> bool: 11 """Return True if the given miles are not in ascending order or contain a zero.""" 12 try: 13 Odometer(miles) 14 except ValueError: 15 return True 16 return False 17 18 @staticmethod 19 def possible_values(odo_len: int) -> List[int]: 20 """Return a list of possible values for the given miles.""" 21 min = Odometer._get_minimum(odo_len) 22 max = Odometer._get_maximum(odo_len) 23 return [i for i in range(min, max + 1) if not Odometer._is_error(i)] 24 25 @staticmethod 26 def _ascending(digits: List[int]) -> bool: 27 return all(digits[i] < digits[i + 1] for i in range(len(digits) - 1)) 28 29 @staticmethod 30 def _get_digits(miles: int) -> List[int]: 31 """Return a list of digits from the given miles.""" 32 return list(map(int, str(miles))) 33 34 @staticmethod 35 def _verify_miles(miles: int) -> None: 36 """Verify that the given miles are in ascending order and do not contain a zero. Throws ValueError if not.""" 37 digits = Odometer._get_digits(miles) 38 if len(digits) > 9: 39 raise ValueError("Miles cannot be greater than 9 digits") 40 if not Odometer._ascending(digits): 41 raise ValueError("Miles must be in ascending order") 42 if any(d == 0 for d in digits): 43 raise ValueError("Miles cannot contain a zero") 44 45 @staticmethod 46 def _get_maximum(odo_len: int) -> int: 47 """Return the maximum possible miles given the current odometer length.""" 48 return int("".join([str(i) for i in range(10 - odo_len, 10)])) 49 50 @staticmethod 51 def _get_minimum(odo_len: int) -> int: 52 """Return the minimum possible miles given the current odometer length.""" 53 return int("".join([str(i) for i in range(1, odo_len + 1)])) 54 55 def __init__(self, starting_miles: int = 1) -> None: 56 """Initialize an odometer with the given starting miles.""" 57 if starting_miles < 0: 58 raise ValueError("Starting miles cannot be negative") 59 Odometer._verify_miles(starting_miles) 60 self.miles = starting_miles 61 62 63 def verify_miles(self) -> bool: 64 """Verify that the odometer's miles are in ascending order and do not contain a zero.""" 65 try: 66 Odometer._verify_miles(self.miles) 67 except ValueError: 68 return False 69 return True 70 71 def get_digits(self) -> List[int]: 72 """Return the odometer's miles as a list of digits.""" 73 return list(map(int, str(self.miles))) 74 75 def get_minimum(self) -> int: 76 """Return the minimum valid odometer reading.""" 77 return Odometer._get_minimum(len(self.get_digits())) 78 79 def get_maximum(self) -> int: 80 """Return the maximum valid odometer reading.""" 81 return Odometer._get_maximum(len(self.get_digits())) 82 83 def next_reading(self) -> Optional[int]: 84 """Return the next valid odometer reading.""" 85 first_run = True 86 if self.miles == self.get_maximum(): 87 return None 88 while not self.verify_miles() or first_run: 89 first_run = False 90 self.miles += 1 91 return self.miles 92 93 def previous_reading(self) -> Optional[int]: 94 """Return the previous valid odometer reading.""" 95 first_run = True 96 if self.miles == self.get_minimum(): 97 return None 98 while not self.verify_miles() or first_run: 99 first_run = False 100 self.miles -= 1 101 return self.miles 102 103 def nth_reading_after(self, n: int) -> Optional[int]: 104 """Return the nth valid odometer reading.""" 105 first_run = True 106 i = 0 107 while not self.verify_miles() or first_run or i < n: 108 if self.miles == self.get_maximum(): 109 return None 110 first_run = False 111 self.miles += 1 112 if self.verify_miles(): 113 i += 1 114 return self.miles 115 116 def nth_reading_before(self, n: int) -> Optional[int]: 117 """Return the nth valid odometer reading before the current one.""" 118 first_run = True 119 i = 0 120 while not self.verify_miles() or first_run or i < n: 121 if self.miles == self.get_minimum(): 122 return None 123 first_run = False 124 self.miles -= 1 125 if self.verify_miles(): 126 i += 1 127 return self.miles 128 129 @staticmethod 130 def print(start: Optional[int], end) -> None: 131 """Print the start of the odometer with some other value.""" 132 print(f"{start} -> {end}") 133 134 @staticmethod 135 def _distance(start: int, end: int) -> int: 136 """Calculate the distance between two odometer readings.""" 137 odo_len = len(Odometer._get_digits(start)) 138 if odo_len != len(Odometer._get_digits(end)): 139 raise ValueError("Odometer readings must have the same number of digits") 140 n = 0 141 while start != end: 142 try: 143 Odometer._verify_miles(start) 144 n += 1 145 if start == Odometer._get_maximum(odo_len): 146 start = Odometer._get_minimum(odo_len) 147 else: 148 start += 1 149 except: 150 start += 1 151 return n 152 153 def distance(self, end: int) -> Optional[int]: 154 """Calculate the distance between the current odometer reading and another.""" 155 try: 156 return Odometer._distance(self.miles, end) 157 except: 158 return None 159 160odometer = Odometer(2467) 161Odometer.print(odometer.miles, odometer.get_minimum()) 162Odometer.print(odometer.miles, odometer.get_maximum()) 163Odometer.print(odometer.miles, odometer.nth_reading_after(6)) 164Odometer.print(odometer.miles, odometer.nth_reading_before(6)) 165Odometer.print(odometer.miles, odometer.next_reading()) 166Odometer.print(odometer.miles, odometer.previous_reading()) 167Odometer.print(odometer.miles, f"1234: {odometer.distance(1234)}") 168 169odo2 = Odometer(123) 170Odometer.print(odo2.miles, odo2.get_minimum()) 171Odometer.print(odo2.miles, odo2.get_maximum()) 172Odometer.print(odo2.miles, odo2.nth_reading_after(83)) 173Odometer.print(odo2.miles, f"123: {odo2.distance(123)}") 174 175print(Odometer.possible_values(8))