Suppose I have a nested list (ndlist
) similar to an N-D array of arbitrary dimensions (let ndim
be the number of dimensions) and a tuple indexes
with len(indexes) == ndim
. If ndlist
was an N-D array I could do the following:
ndlist[indexes] = (some object)
What is the equivalent for a list? Note that ndim
is arbitrary so I can't hardcode it like this:
ndlist[indexes[0]][indexes[1]]... = (some object)
Here's an example for ndim == 3:
ndlist = [[[10, 10], [10, 10]],[[10, 10], [10, 10]]] % 3-D (2x2x2) list with all elements equal to 10
When I know ndim beforehand I can edit the (0,0,0) element of ndlist like this:
ndlist[0][0][0] = 11 %changing from 10 to 11 requires 3 [0]s in sequence
Now suppose ndims == 4 (4-dimensional list). Editing the the (0,0,0,0) element of ndlist would require something like this:
ndlist[0][0][0][0] = 11 %change to 11 requires 4 [0]s in sequence
And for arbitrary ndims:
ndlist[0][0][0]...[0] = 11 %change to 11 requires ndim [0]s in sequence
As you see, I can't index the list that way for the general case where ndim is not known in advance, as it requires to type as many [0] as ndim.
If instead of the list I had an array like this:
ndarray = np.array(ndlist)
Accessing the (0, 0, 0, ... ,0) would not be an issue since I can using a tuple to index all dimensions simultaneously like that:
% 3d case
indexes = (0,0,0)
ndarray[indexes]
% 4d case
indexes = (0,0,0,0)
ndarray[indexes]
% n-d case
indexes = (0, 0, 0, ... ,0) % I can generate this with code
ndarray[indexes] = 11
Is there a way to index a list with a single tuple like that? Even better, can the tuple hold slices instead of indexes? For instance arrays also allow this:
ndarray[0:2, 0, 0] = np.array([0, 0])
The only solution have found to my problem is to use recursion to index one dimension at a time. Is there a better solution? Thanks!
CodePudding user response:
Now I understood the problem. If you are willing to have a function donig that for you it will be easy. Otherwise you'll need to create your own list type.
Function
You will need a function which takes a list and n
(unknown) number of elements. The n
means you will need *argv
The function will get the list and get the i
th element in n
(argv
).
def get_element(the_list, *argv):
sub_list = the_list.copy()
for i in argv:
sub_list = sub_list[i]
return sub_list
Note: The function copies the original list
to make sure it's not changed.
Note: You will need to handle the index out of range
error too. Right now it will raise an error of TypeError: 'blabla' object is not subscriptable
.
Your own list type
Here you will create a class with any name (let's sat TypedList
) which inherits from list
and override the __getitem__
method.
class TypedList(list):
def __getitem__(self, keys):
sub_list = self.copy()
for i in keys:
sub_list = sub_list[i]
return sub_list
if __name__ == '__main__':
ndlist = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
lst = TypedList(ndlist)
print(lst[1, 1, 0])
Here you will need handle the index out of range
error too.
Edit after the comment.
You are very right that I actually did not answer the question. And Since you already accepted my answer I am going to find a way to do it.
Some edits
My implementation has a small (Actually huge) problem in it. If you use just an integer as the index it will fail. So the code must change as:
class TypedList(list):
def __getitem__(self, keys):
sub_list = self.copy()
if isinstance(keys, int):
return sub_list[keys]
for i in keys:
sub_list = sub_list[i]
return sub_list
Now. I must clarify that
Python doesn't copy objects you pass during a function call ever.
What does that mean? IF you modify the passed object to a function, the object itself will change. Notice how I copied lists first then worked on them. See: https://stackoverflow.com/a/575337/2681662
We are going to get advantage of this with writing a function that actually modifies the passed object:
def set_value(the_list, keys, value):
for i in range(len(keys) - 1):
the_list = the_list[keys[i]]
the_list[keys[-1]] = value
Now the only thing is to do is to somehow manage to make this function a method of our class and use it from __setitem__
class TypedList(list):
def __set(self, the_list, keys, value):
for i in range(len(keys) - 1):
the_list = the_list[keys[i]]
the_list[keys[-1]] = value
def __setitem__(self, keys, value):
if isinstance(keys, int):
super().__setitem__(keys, value) # <- This has to be done. Otherwise maximum recursion depth would occurre...
else:
self.__set(self, keys, value)
def __getitem__(self, keys):
sub_list = self.copy()
if isinstance(keys, int):
return sub_list[keys]
for i in keys:
sub_list = sub_list[i]
return sub_list
if __name__ == '__main__':
ndlist = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
lst = TypedList(ndlist)
lst[1, 1, 0] = 22
print(lst)
Here we have a private method called __set
and will be called by __setitem__
to modify self
which is a type of list
.