I'm trying to build accessors for a kd nested list-of-lists such as this 4D example (of shape \$ 2 \times 2 \times 2 \times 2 \$):
kd_lol = [
[
[
[0,1],
[2,3]
],
[
[4,5],
[6,7]
]
],
[
[
[8,9],
[10,11]
],
[
[12,13],
[14,15]
]
]
]
I use a list of values to access and modify elements in this nested list-of-lists.
In the above example, you can access something using [i,j,k,l]
where \$ 0 \le i,j,k,l < 2 \$.
I'd like to build a fast, efficient getter in idiomatic Python of the form:
def get(grid, list_of_indices)
def put(grid, list_of_indices, value)
My current implementation is as follows:
def get(grid, list_of_indices):
cur_grid = grid
for index in list_of_indices:
cur_grid = cur_grid[index]
assert(type(cur_grid) != type([])) # not a list
return cur_grid
and
def put(grid, list_of_indices, value):
cur_grid = grid
for index in list_of_indices[:-1]:
cur_grid = cur_grid[index]
assert(type(cur_grid) == type([])) # must be a 1D list
assert(type(cur_grid[0]) != type([])) # must be a 1D list
cur_grid[list_of_indices[-1]] = value
Any critique/suggestions to tidy up or reduce the amount of code above is welcome.
Here's a repl that you can play with.
-
\$\begingroup\$ Would you kindly change 'list-of-lists' to 'matrix', since that's the formal definition? \$\endgroup\$Daniel– Daniel2017年10月17日 20:41:49 +00:00Commented Oct 17, 2017 at 20:41
-
1\$\begingroup\$ @Coal_, from Wikipedia a matrix typically refers to a 2D structure only. Math SE doesn't seem certain either. I'll leave this edit to the wisdom of CRSE. \$\endgroup\$Jedi– Jedi2017年10月18日 03:40:22 +00:00Commented Oct 18, 2017 at 3:40
1 Answer 1
Some very nice code, overall. There's some minor points I think could be improved, but your general implementation is good.
Nitpicks
I'd like to point out some small 'issues' regarding best-practice and code style:
PEP8 recommends using 4 spaces for indentation. They're not too fond of tabs, but most editors will replace tabs with spaces automatically.
Using
assert
is great for catching bugs, but it'd better toraise
an exception instead when releasing your code. This allows us to specify a specific exception type (likeValueError
orTypeError
) and provides some additional stack trace information.It's important to use good names for objects. You're almost there, but a name like
cur_grid
may as well just be calledcurrent_grid
, for clarity.Instead of manually comparing
type
, we can useisinstance
which allows something like this:assert not isinstance(curr_grid, list)
.
Implementation
The code as is works just fine if you need to modify a matrix, but wouldn't it be useful to turn it into a separate object altogether? We can do this by creating a custom class
:
import collections
class Matrix(collections.UserList):
"""A Matrix represents a collection of nested list objects.
Methods:
get -- Get a value from the matrix.
put -- Put a value into the matrix.
"""
def __init__(self, data):
"""Constructor.
:param list data: a matrix (multi-dimensional list).
:return: None
"""
super(Matrix, self).__init__(data)
def get(self, indices):
"""Get a value from the matrix.
:param iterable indices: iterable containing indices.
the last index must point to an object
that is not a list.
:return: matrix at indices[-1]
:raises TypeError: if the last index points to a list
"""
grid = self.data
# As assigned by UserList
for index in indices:
grid = grid[index]
if isinstance(grid, list):
raise TypeError("last element is not 1-dimensional")
return grid
def put(self, indices, value):
"""Put value at matrix[indices].
:param iterable indices: iterable containing indices.
the last index must point to a list.
:return: None
:raises TypeError: if the last element is not a list
:raises TypeError: if the last element contains a list
"""
grid = self.data
for index in indices[:-1]:
grid = grid[index]
if not isinstance(grid, list):
error = "expected 'list' node, but got '{}'".format(type(grid))
raise TypeError(error)
if isinstance(grid[0], list):
raise TypeError("last element is not 1-dimensional")
grid[indices[-1]] = value
Note: the class as implemented must be initialized with a matrix and breaks if an empty iterable is passed for data
.