CMU Coding Bootcamp

feat: oct 10 level 0

thecoded.prof 8e48c8aa 72d84fb1

verified
Changed files
+175
python
oct10
level0
+175
python/oct10/level0/odometer.py
··· 1 + from typing import List, Optional 2 + 3 + 4 + class 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 + 160 + odometer = Odometer(2467) 161 + Odometer.print(odometer.miles, odometer.get_minimum()) 162 + Odometer.print(odometer.miles, odometer.get_maximum()) 163 + Odometer.print(odometer.miles, odometer.nth_reading_after(6)) 164 + Odometer.print(odometer.miles, odometer.nth_reading_before(6)) 165 + Odometer.print(odometer.miles, odometer.next_reading()) 166 + Odometer.print(odometer.miles, odometer.previous_reading()) 167 + Odometer.print(odometer.miles, f"1234: {odometer.distance(1234)}") 168 + 169 + odo2 = Odometer(123) 170 + Odometer.print(odo2.miles, odo2.get_minimum()) 171 + Odometer.print(odo2.miles, odo2.get_maximum()) 172 + Odometer.print(odo2.miles, odo2.nth_reading_after(83)) 173 + Odometer.print(odo2.miles, f"123: {odo2.distance(123)}") 174 + 175 + print(Odometer.possible_values(8))