possibleData = str | float | None class DataList: data: list[possibleData] def __init__(self, L: list[possibleData]) -> None: self.data = L def __getitem__(self, index: int) -> possibleData: return self.data[index] def __len__(self) -> int: return len(self.data) def numericValues(self) -> list[float | int]: items = [item for item in self.data if isinstance(item, float | int)] return items def min(self) -> float: return sorted(self.numericValues())[0] def average(self) -> float: nvs = self.numericValues() return sum(nvs) / len(nvs) def median(self) -> float | None: nvs = self.numericValues() if len(nvs) == 0: return None snvs = list(sorted(nvs)) middle = len(nvs) // 2 if len(nvs) % 2 == 1: return snvs[middle] else: sl = snvs[middle - 1 : middle + 1] return sum(sl) / 2 def max(self) -> float: return list(reversed(sorted(self.numericValues())))[0] def range(self) -> tuple[float, float]: return (self.min(), self.max()) @property def values(self) -> list[float | int | None]: values = [item for item in self.data if (isinstance(item, float | int | None))] return values class Table: data: list[DataList] def __init__(self, data: list[list[possibleData]]) -> None: self.data = [] m, n = len(data), len(data[0]) result: list[list[possibleData]] = [] for col in range(n): result.append([]) for row in range(m): result[col].append(data[row][col]) for row in result: dl = DataList(row) self.data.append(dl) def getHeaders(self) -> list[str]: return [str(r[0]) for r in self.data] def _getColInt(self, index: int) -> DataList: if index >= len(self.data): raise IndexError(f"Col {index} is out of range") return self.data[index] def _getColStr(self, index: str) -> DataList: hi = self.getHeaderIndex(index) return self._getColInt(hi) def getCol(self, index: int | str) -> DataList: if isinstance(index, int): return self._getColInt(index) else: return self._getColStr(index) def getRow(self, index: int) -> list[possibleData]: if index + 1 >= len(self.data[0]): raise IndexError(f"Row {index} is out of range") else: row = [c[index + 1] for c in self.data] return row def getHeaderIndex(self, header: str) -> int: headers = [c[0] for c in self.data] for i, h in enumerate(headers): if h == header: return i else: raise KeyError(f"No such header: {header}") def almostEqual(x: float | None, y: float | None) -> bool: if x == None or y == None: return False epsilon = 10**-9 return abs(x - y) < epsilon def testLevel1(): print("Testing Level 1 (Core) material...", end="") # First test DataList: dl = DataList([5, 2, None, 1.1, "yikes", 3.9]) assert type(dl) == DataList assert dl.numericValues() == [5, 2, 1.1, 3.9] assert dl.min() == 1.1 assert dl.max() == 5 # Now test Table and TableRow weatherData: list[list[possibleData]] = [ ["Date", "Low", "High"], ["1-Aug", 63, 81], ["2-Aug", 67, 85], ["3-Aug", 64, 86], ["4-Aug", 61, None], ["5-Aug", None, None], ["6-Aug", 59, 71], ["7-Aug", 63, 77], ["8-Aug", 68, 88], ["9-Aug", 75, 91], ["10-Aug", 74, 93], ] table = Table(weatherData) assert table.getHeaders() == ["Date", "Low", "High"] lows = table.getCol(1) assert type(lows) == DataList assert lows.values == [63, 67, 64, 61, None, 59, 63, 68, 75, 74] assert lows.numericValues() == [63, 67, 64, 61, 59, 63, 68, 75, 74] assert lows.min() == 59 assert lows.max() == 75 highs = table.getCol(2) assert type(highs) == DataList assert highs.values == [81, 85, 86, None, None, 71, 77, 88, 91, 93] assert highs.numericValues() == [81, 85, 86, 71, 77, 88, 91, 93] assert highs.min() == 71 assert highs.max() == 93 # We will try to get col 3, but that is out of range, # so it will raise a custom exception. error = None try: _col = table.getCol(3) except Exception as e: error = str(e) assert error == "Col 3 is out of range" print("Passed!") def testLevel2(): print("Testing Level 2+ (Not Core) material...", end="") # First test DataList: dl = DataList([5, 2, None, 1.1, "yikes", 3.9]) assert dl.range() == (1.1, 5) assert almostEqual(dl.average(), 3) # Now test Table and TableRow weatherData: list[list[possibleData]] = [ ["Date", "Low", "High"], ["1-Aug", 63, 81], ["2-Aug", 67, 85], ["3-Aug", 64, 86], ["4-Aug", 61, None], ["5-Aug", None, None], ["6-Aug", 59, 71], ["7-Aug", 63, 77], ["8-Aug", 68, 88], ["9-Aug", 75, 91], ["10-Aug", 74, 93], ] table = Table(weatherData) assert table.getHeaders() == ["Date", "Low", "High"] assert table.getRow(0) == ["1-Aug", 63, 81] assert table.getRow(9) == ["10-Aug", 74, 93] # We will try to get row 10, but that is out of range, # so it will raise a custom exception. error = None try: _row = table.getRow(10) except Exception as e: error = str(e) assert error == "Row 10 is out of range" assert table.getHeaderIndex("Date") == 0 assert table.getHeaderIndex("Low") == 1 assert table.getHeaderIndex("High") == 2 # We will try to find the header index of 'Missing', but # there is no such header, so it will raise a custom exception. error = None try: _i = table.getHeaderIndex("Missing") except Exception as e: error = str(e) # assert(error == 'No such header: Missing') lows = table.getCol("Low") # hint: use getHeaderIndex! assert type(lows) == DataList assert lows.values == [63, 67, 64, 61, None, 59, 63, 68, 75, 74] assert lows.range() == (59, 75) assert almostEqual(lows.average(), 66) highs = table.getCol("High") assert type(highs) == DataList assert highs.values == [81, 85, 86, None, None, 71, 77, 88, 91, 93] assert highs.range() == (71, 93) assert almostEqual(highs.average(), 84) # And for one last challenge: # We define the MEDIAN of a list like so: # * If the list has odd length, then the median is # the middle value of the sorted list. # * If the list has even length, then the median is # the average of the two middle values of the sorted list. # For example: # median([11, 19, 7, 14, 3]) # * This list has length 5, which is odd, so the median is the # middle value of the sorted list, [3, 7, 11, 14, 19], # which is 11. # median([11, 19, 7, 14]) # * This list has length 4, which is even, so the median is the # average of the two middle value of the sorted list, # [7, 11, 14, 19], which is (11 + 14)/2 = 25/2 = 12.5. # # With this in mind, pass these additional tests; dl = DataList([11, 19, 7, 14, 3]) assert almostEqual(dl.median(), 11) dl = DataList([11, 19, 7, 14]) assert almostEqual(dl.median(), 12.5) dl = DataList([]) assert dl.median() == None # No values, so no median assert almostEqual(lows.median(), 64) assert almostEqual(highs.median(), 85.5) print("Passed!") def main(): testLevel1() testLevel2() main()