-
Notifications
You must be signed in to change notification settings - Fork 0
/
table.py
206 lines (191 loc) · 6.15 KB
/
table.py
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
"""Provides a 2D table of values"""
import itertools
class Table:
"""2D table of values with m columns and n rows.
>>> Table(2, 2)
Table 2x2:
[0, 0]
[0, 0]
>>> Table(2, 2, 1)
Table 2x2:
[1, 1]
[1, 1]
>>> Table(3, 2)
Table 3x2:
[0, 0, 0]
[0, 0, 0]
>>> Table(2, 3)
Table 2x3:
[0, 0]
[0, 0]
[0, 0]
>>> Table(2,0)
Traceback (most recent call last):
...
ValueError: Table size cannot be smaller than 1
>>> Table(1,-1)
Traceback (most recent call last):
...
ValueError: Table size cannot be smaller than 1
>>> t = Table(3, 4)
>>> t.num_rows
4
>>> t.num_columns
3
"""
def __init__(self, columns, rows, initial=0):
if columns <= 0 or rows <= 0:
raise ValueError('Table size cannot be smaller than 1')
self.table = [initial for c in range(columns*rows)]
self.num_columns = columns
self.num_rows = rows
def size(self):
"""Returns the dimensions of the table as a tuple."""
return (self.num_columns, self.num_rows)
def __getitem__(self, key):
"""Returns value of a cell.
Key can either be a tuple of (row, column) or a linear index (i.e. column + row*num_columns).
>>> t = Table.from_nested_list([[0, 1, 2], [3, 4, 5]])
>>> t[0, 0]
0
>>> t[1,1]
4
>>> t[2,0]
2
>>> t[0,1]
3
>>> t[3,0]
Traceback (most recent call last):
...
IndexError: list index out of range
>>> t[0,2]
Traceback (most recent call last):
...
IndexError: list index out of range
>>> t[0,-1]
3
>>> t[0]
0
>>> t[5]
5
"""
if isinstance(key, slice):
raise TypeError("Slicing is not supported")
else:
try:
x, y = key
if x >= self.num_columns or y >= self.num_rows:
raise IndexError('list index out of range')
return self.table[self.subscript_to_linear(x, y)]
except TypeError:
return self.table[key]
def __setitem__(self, key, value):
"""Overrides the value of a cell.
Key can either be a tuple of (row, column) or a linear index (i.e. column + row*num_columns).
>>> t = Table(3, 2)
>>> t[0, 0] = 4
>>> t[1, 0] = 5
>>> t[0, 1] = 6
>>> t
Table 3x2:
[4, 5, 0]
[6, 0, 0]
>>> t[4, 0] = 1
Traceback (most recent call last):
...
IndexError: list index out of range
>>> t[4] = 1
>>> t
Table 3x2:
[4, 5, 0]
[6, 1, 0]
"""
if isinstance(key, slice):
raise TypeError("Slicing is not supported")
else:
try:
x, y = key
if x >= self.num_columns or y >= self.num_rows:
raise IndexError('list index out of range')
self.table[self.subscript_to_linear(x, y)] = value
except TypeError:
self.table[key] = value
def neighbors(self, x, y):
"""Returns a generator for all direct neighbors of a cell.
Does not return the cell itself.
Does not generate points outside the table for cells at the border.
"""
for c in range(max(x-1, 0), min(x+2, self.num_columns)):
for r in range(max(y-1, 0), min(y+2, self.num_rows)):
if c != x or r != y:
yield (c, r)
def linear_to_subscript(self, index):
"""Converts a linear index to a supscript index of (column, row)."""
return (index % self.num_columns, index // self.num_columns)
def subscript_to_linear(self, column, row):
"""Converts a supscript index (column, row) to a linear index."""
return column + row*self.num_columns
def count(self, value):
"""Returns the number of occurrences of value in the table."""
return self.table.count(value)
def row(self, r):
"""Returns a row as a list.
>>> t = Table.from_nested_list([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]])
>>> t.row(0)
[0, 1, 2, 3]
>>> t.row(2)
[8, 9, 10, 11]
"""
row_start = self.subscript_to_linear(0, r)
return self.table[row_start:row_start+self.num_columns]
def __eq__(self, other):
"""Returns true if other table has the same dimensions and content."""
try:
return (
self.num_columns == other.num_columns and
self.num_rows == other.num_rows and
self.table == other.table
)
except AttributeError:
return False
def __repr__(self):
"""Returns the table as a string.
>>> Table.from_nested_list([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]])
Table 4x3:
[0, 1, 2, 3]
[4, 5, 6, 7]
[8, 9, 10, 11]
"""
s = 'Table {0}x{1}:\n'.format(self.num_columns, self.num_rows)
for r in range(self.num_rows):
s += repr(self.row(r)) + '\n'
return s[:-1] #remove last newline
def __iter__(self):
"""Iterates the table in a column major order.
>>> t = Table.from_nested_list([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]])
>>> [c for c in t]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
"""
for y in range(self.num_rows):
for x in range(self.num_columns):
yield self[x,y]
@classmethod
def from_nested_list(cls, list_of_list):
"""
>>> t = Table.from_nested_list([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]])
>>> t.num_columns
4
>>> t.num_rows
3
>>> t
Table 4x3:
[0, 1, 2, 3]
[4, 5, 6, 7]
[8, 9, 10, 11]
"""
# check if all internal list have the same length
if len(set([len(l) for l in list_of_list])) > 1:
raise ValueError('Rows cannot have different length')
t = Table(len(list_of_list[0]), len(list_of_list))
t.table = list(itertools.chain(*list_of_list))
return t