forked from fluentpython/example-code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtable.py
More file actions
173 lines (143 loc) · 4.46 KB
/
table.py
File metadata and controls
173 lines (143 loc) · 4.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
"""
=============
Row tests
=============
>>> row = Row([1, 2, 3, 4])
>>> row[1]
2
>>> row[1:3]
Row([2, 3])
=============
Table tests
=============
Create an empty table
>>> t3x4 = Table.blank(3, 4)
>>> t3x4
Table(Row([None, None, None, None]),
Row([None, None, None, None]),
Row([None, None, None, None]))
>>> for i in range(3):
... for j in range(4):
... t3x4[i][j] = chr(65 + i * 4 + j)
...
>>> t3x4
Table(Row(['A', 'B', 'C', 'D']),
Row(['E', 'F', 'G', 'H']),
Row(['I', 'J', 'K', 'L']))
>>> t3x4[1]
Row(['E', 'F', 'G', 'H'])
>>> t3x4[1:]
Table(Row(['E', 'F', 'G', 'H']),
Row(['I', 'J', 'K', 'L']))
>>> t3x4[1][2]
'G'
>>> t3x4[1, 2]
'G'
Slicing returns a table, so index 2 below would be trying to get row index 2
of a table that has only rows 0 and 1:
>>> t3x4[1:][2]
Traceback (most recent call last):
...
IndexError: no row at index 2 of 2-row table
>>> t3x4[:, 2]
Table(Row(['C']),
Row(['G']),
Row(['K']))
>>> t3x4[1:, 2]
Table(Row(['G']),
Row(['K']))
>>> t3x4[1, 2:]
Row(['G', 'H'])
>>> t3x4[:, 1:3]
Table(Row(['B', 'C']),
Row(['F', 'G']),
Row(['J', 'K']))
>>> t3x4[:, :]
Table(Row(['A', 'B', 'C', 'D']),
Row(['E', 'F', 'G', 'H']),
Row(['I', 'J', 'K', 'L']))
>>> t3x4[:, :] == t3x4
True
===============
Error handling
===============
>>> t3x4[5]
Traceback (most recent call last):
...
IndexError: no row at index 5 of 3-row table
>>> t3x4[1,]
Traceback (most recent call last):
...
IndexError: index must be [i] or [i, j]
>>> t3x4[1, 2, 3]
Traceback (most recent call last):
...
IndexError: index must be [i] or [i, j]
>>> t3x4[10:, 2]
Traceback (most recent call last):
...
ValueError: Table must have at least one row.
>>> t3x4[1, 20:]
Traceback (most recent call last):
...
ValueError: Row must have at least one cell.
"""
import collections
class Row(collections.UserList):
def __init__(self, cells):
super().__init__(cells)
if len(self) < 1:
raise ValueError('Row must have at least one cell.')
def __getitem__(self, position):
if isinstance(position, slice):
return Row(self.data[position]) # build sub-row
else:
return self.data[position] # return cell value
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, self.data)
class Table(collections.UserList):
"""A table with rows, all of the same width"""
def __init__(self, rows):
super().__init__(Row(r) for r in rows)
if len(self) < 1:
raise ValueError('Table must have at least one row.')
self.width = self.check_width()
def check_width(self):
row_widths = {len(row) for row in self.data}
if len(row_widths) > 1:
raise ValueError('All rows must have equal length.')
return row_widths.pop()
@classmethod
def blank(class_, rows, columns, filler=None):
return class_([[filler] * columns for i in range(rows)])
def __repr__(self):
prefix = '%s(' % self.__class__.__name__
indent = ' ' * len(prefix)
rows = (',\n' + indent).join(
repr(row) for row in self.data)
return prefix + rows + ')'
def _get_indexes(self, position):
if isinstance(position, tuple): # multiple indexes
if len(position) == 2: # two indexes: t[i, j]
return position
else:
raise IndexError('index must be [i] or [i, j]')
else: # one index: t[i]
return position, None
def __getitem__(self, position):
i, j = self._get_indexes(position)
if isinstance(i, slice):
if j is None: # build sub-table w/ full rows
return Table(self.data[position])
else: # build sub-table w/ sub-rows
return Table(cells[j] for cells in self.data[i])
else: # i is number
try:
row = self.data[i]
except IndexError:
msg = 'no row at index %r of %d-row table'
raise IndexError(msg % (position, len(self)))
if j is None: # return row at table[i]
return row
else:
return row[j] # return row[j] or row[a:b]