+255
python/oct10/Tables/main.py
+255
python/oct10/Tables/main.py
···
1
+
possibleData = str | float | None
2
+
3
+
4
+
class DataList:
5
+
data: list[possibleData]
6
+
7
+
def __init__(self, L: list[possibleData]) -> None:
8
+
self.data = L
9
+
10
+
def __getitem__(self, index: int) -> possibleData:
11
+
return self.data[index]
12
+
13
+
def __len__(self) -> int:
14
+
return len(self.data)
15
+
16
+
def numericValues(self) -> list[float | int]:
17
+
items = [item for item in self.data if isinstance(item, float | int)]
18
+
return items
19
+
20
+
def min(self) -> float:
21
+
return sorted(self.numericValues())[0]
22
+
23
+
def average(self) -> float:
24
+
nvs = self.numericValues()
25
+
return sum(nvs) / len(nvs)
26
+
27
+
def median(self) -> float | None:
28
+
nvs = self.numericValues()
29
+
if len(nvs) == 0:
30
+
return None
31
+
snvs = list(sorted(nvs))
32
+
middle = len(nvs) // 2
33
+
if len(nvs) % 2 == 1:
34
+
return snvs[middle]
35
+
else:
36
+
sl = snvs[middle - 1 : middle + 1]
37
+
return sum(sl) / 2
38
+
39
+
def max(self) -> float:
40
+
return list(reversed(sorted(self.numericValues())))[0]
41
+
42
+
def range(self) -> tuple[float, float]:
43
+
return (self.min(), self.max())
44
+
45
+
@property
46
+
def values(self) -> list[float | int | None]:
47
+
values = [item for item in self.data if (isinstance(item, float | int | None))]
48
+
return values
49
+
50
+
51
+
class Table:
52
+
data: list[DataList]
53
+
54
+
def __init__(self, data: list[list[possibleData]]) -> None:
55
+
self.data = []
56
+
m, n = len(data), len(data[0])
57
+
result: list[list[possibleData]] = []
58
+
for col in range(n):
59
+
result.append([])
60
+
for row in range(m):
61
+
result[col].append(data[row][col])
62
+
for row in result:
63
+
dl = DataList(row)
64
+
self.data.append(dl)
65
+
66
+
def getHeaders(self) -> list[str]:
67
+
return [str(r[0]) for r in self.data]
68
+
69
+
def _getColInt(self, index: int) -> DataList:
70
+
if index >= len(self.data):
71
+
raise IndexError(f"Col {index} is out of range")
72
+
return self.data[index]
73
+
74
+
def _getColStr(self, index: str) -> DataList:
75
+
hi = self.getHeaderIndex(index)
76
+
return self._getColInt(hi)
77
+
78
+
def getCol(self, index: int | str) -> DataList:
79
+
if isinstance(index, int):
80
+
return self._getColInt(index)
81
+
else:
82
+
return self._getColStr(index)
83
+
84
+
def getRow(self, index: int) -> list[possibleData]:
85
+
if index + 1 >= len(self.data[0]):
86
+
raise IndexError(f"Row {index} is out of range")
87
+
else:
88
+
row = [c[index + 1] for c in self.data]
89
+
return row
90
+
91
+
def getHeaderIndex(self, header: str) -> int:
92
+
headers = [c[0] for c in self.data]
93
+
for i, h in enumerate(headers):
94
+
if h == header:
95
+
return i
96
+
else:
97
+
raise KeyError(f"No such header: {header}")
98
+
99
+
100
+
def almostEqual(x: float | None, y: float | None) -> bool:
101
+
if x == None or y == None:
102
+
return False
103
+
epsilon = 10**-9
104
+
return abs(x - y) < epsilon
105
+
106
+
107
+
def testLevel1():
108
+
print("Testing Level 1 (Core) material...", end="")
109
+
# First test DataList:
110
+
dl = DataList([5, 2, None, 1.1, "yikes", 3.9])
111
+
assert type(dl) == DataList
112
+
assert dl.numericValues() == [5, 2, 1.1, 3.9]
113
+
assert dl.min() == 1.1
114
+
assert dl.max() == 5
115
+
116
+
# Now test Table and TableRow
117
+
weatherData: list[list[possibleData]] = [
118
+
["Date", "Low", "High"],
119
+
["1-Aug", 63, 81],
120
+
["2-Aug", 67, 85],
121
+
["3-Aug", 64, 86],
122
+
["4-Aug", 61, None],
123
+
["5-Aug", None, None],
124
+
["6-Aug", 59, 71],
125
+
["7-Aug", 63, 77],
126
+
["8-Aug", 68, 88],
127
+
["9-Aug", 75, 91],
128
+
["10-Aug", 74, 93],
129
+
]
130
+
table = Table(weatherData)
131
+
assert table.getHeaders() == ["Date", "Low", "High"]
132
+
133
+
lows = table.getCol(1)
134
+
assert type(lows) == DataList
135
+
assert lows.values == [63, 67, 64, 61, None, 59, 63, 68, 75, 74]
136
+
assert lows.numericValues() == [63, 67, 64, 61, 59, 63, 68, 75, 74]
137
+
assert lows.min() == 59
138
+
assert lows.max() == 75
139
+
140
+
highs = table.getCol(2)
141
+
assert type(highs) == DataList
142
+
assert highs.values == [81, 85, 86, None, None, 71, 77, 88, 91, 93]
143
+
assert highs.numericValues() == [81, 85, 86, 71, 77, 88, 91, 93]
144
+
assert highs.min() == 71
145
+
assert highs.max() == 93
146
+
147
+
# We will try to get col 3, but that is out of range,
148
+
# so it will raise a custom exception.
149
+
error = None
150
+
try:
151
+
col = table.getCol(3)
152
+
except Exception as e:
153
+
error = str(e)
154
+
assert error == "Col 3 is out of range"
155
+
print("Passed!")
156
+
157
+
158
+
def testLevel2():
159
+
print("Testing Level 2+ (Not Core) material...", end="")
160
+
# First test DataList:
161
+
dl = DataList([5, 2, None, 1.1, "yikes", 3.9])
162
+
assert dl.range() == (1.1, 5)
163
+
assert almostEqual(dl.average(), 3)
164
+
165
+
# Now test Table and TableRow
166
+
weatherData: list[list[possibleData]] = [
167
+
["Date", "Low", "High"],
168
+
["1-Aug", 63, 81],
169
+
["2-Aug", 67, 85],
170
+
["3-Aug", 64, 86],
171
+
["4-Aug", 61, None],
172
+
["5-Aug", None, None],
173
+
["6-Aug", 59, 71],
174
+
["7-Aug", 63, 77],
175
+
["8-Aug", 68, 88],
176
+
["9-Aug", 75, 91],
177
+
["10-Aug", 74, 93],
178
+
]
179
+
table = Table(weatherData)
180
+
assert table.getHeaders() == ["Date", "Low", "High"]
181
+
182
+
assert table.getRow(0) == ["1-Aug", 63, 81]
183
+
assert table.getRow(9) == ["10-Aug", 74, 93]
184
+
185
+
# We will try to get row 10, but that is out of range,
186
+
# so it will raise a custom exception.
187
+
error = None
188
+
try:
189
+
row = table.getRow(10)
190
+
except Exception as e:
191
+
error = str(e)
192
+
assert error == "Row 10 is out of range"
193
+
194
+
assert table.getHeaderIndex("Date") == 0
195
+
assert table.getHeaderIndex("Low") == 1
196
+
assert table.getHeaderIndex("High") == 2
197
+
198
+
# We will try to find the header index of 'Missing', but
199
+
# there is no such header, so it will raise a custom exception.
200
+
error = None
201
+
try:
202
+
i = table.getHeaderIndex("Missing")
203
+
except Exception as e:
204
+
error = str(e)
205
+
# assert(error == 'No such header: Missing')
206
+
207
+
lows = table.getCol("Low") # hint: use getHeaderIndex!
208
+
assert type(lows) == DataList
209
+
assert lows.values == [63, 67, 64, 61, None, 59, 63, 68, 75, 74]
210
+
assert lows.range() == (59, 75)
211
+
assert almostEqual(lows.average(), 66)
212
+
213
+
highs = table.getCol("High")
214
+
assert type(highs) == DataList
215
+
assert highs.values == [81, 85, 86, None, None, 71, 77, 88, 91, 93]
216
+
assert highs.range() == (71, 93)
217
+
assert almostEqual(highs.average(), 84)
218
+
219
+
# And for one last challenge:
220
+
# We define the MEDIAN of a list like so:
221
+
# * If the list has odd length, then the median is
222
+
# the middle value of the sorted list.
223
+
# * If the list has even length, then the median is
224
+
# the average of the two middle values of the sorted list.
225
+
# For example:
226
+
# median([11, 19, 7, 14, 3])
227
+
# * This list has length 5, which is odd, so the median is the
228
+
# middle value of the sorted list, [3, 7, 11, 14, 19],
229
+
# which is 11.
230
+
# median([11, 19, 7, 14])
231
+
# * This list has length 4, which is even, so the median is the
232
+
# average of the two middle value of the sorted list,
233
+
# [7, 11, 14, 19], which is (11 + 14)/2 = 25/2 = 12.5.
234
+
#
235
+
# With this in mind, pass these additional tests;
236
+
dl = DataList([11, 19, 7, 14, 3])
237
+
assert almostEqual(dl.median(), 11)
238
+
239
+
dl = DataList([11, 19, 7, 14])
240
+
assert almostEqual(dl.median(), 12.5)
241
+
242
+
dl = DataList([])
243
+
assert dl.median() == None # No values, so no median
244
+
245
+
assert almostEqual(lows.median(), 64)
246
+
assert almostEqual(highs.median(), 85.5)
247
+
print("Passed!")
248
+
249
+
250
+
def main():
251
+
testLevel1()
252
+
testLevel2()
253
+
254
+
255
+
main()