Pass a tuple of positive integers of length N (any length) as dimensions to MDarray, and a list is created with the length equal to the product NNN of the dimensions specified in the tuple. The items are initialized to None, or to the value of the optional init parameter. As a special case, passing an integer for dims creates a square array of that size (converts the integer to a tuple of length two with the same value for both entries).
You can then index the list with single numbers in range(NNN), or by a tuple of the same length N with which the list was created.
ary = MDarray(( 5, 6, 8 ), 0 )
ix = ary.index_from_tuple(( 2, 4, 3 ))
print( ix ) # 87
print( ary[ 2, 4, 2 ], ary[ 2, 4, 3 ], ary[ 2, 4, 4 ]) # 0, 0, 0
print( ary[ 86 ], ary[ 87 ], ary[ 88 ]) # 0, 0, 0
ary[ 2, 4, 3 ] = 25
print( ary[ 2, 4, 2 ], ary[ 2, 4, 3 ], ary[ 2, 4, 4 ]) # 0, 25, 0
print( ary[ 86 ], ary[ 87 ], ary[ 88 ]) # 0, 25, 0
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 | #####################################
class MDarray( list ): # version 12.5.11
"""Allow multi-dimensional tuples to be passed as indexes to a list.
All other operations must use real indexes, which can be calculated
using index_from_tuple().
The constructor requires a tuple of positive integers to define the
dimensions of the array (and hence the size of the underlying list).
The size of the list should not be changed, to keep the multi-dimensional
nature intact.
"""
def __init__( self, dims, init=None ):
if isinstance( dims, tuple ):
self.dims = dims
else:
self.dims = (dims, dims)
self.size = 1
for ix in self.dims:
if ix < 1:
raise ValueError('each dimension must be a positive integer')
self.size *= ix
list.__init__( self, [ init ] * self.size )
def index_from_tuple( self, dims ):
rix = dims[ 0 ]
for iy in range( len( self.dims ) - 1 ):
rix = rix * self.dims[ iy ] + dims[ iy + 1 ]
return rix
def __getitem__( self, ix ):
if isinstance( ix, tuple ):
rix = self.index_from_tuple( ix )
else:
rix = ix
return list.__getitem__( self, rix )
def __setitem__( self, ix, val ):
if isinstance( ix, tuple ):
rix = self.index_from_tuple( ix )
else:
rix = ix
list.__setitem__( self, rix, val )
def __repr__( self ):
return ( list.__repr__( self )
+ ' dims: ' + self.dims.__repr__()
+ ' size: ' + self.size.__repr__()
)
#####################################
|
I searched and searched for a two dimensional view of a list, that I had seen once here or stackoverflow or somewhere, that implemented two dimensions by having [] return a subclass that knew which row of the array, and how many items per row, and then the second [] could do the indexing math, and return the appropriate item.
When I couldn't find it, I saw instead a reference (but no code) for using a two dimensional tuple as the index into the list... which got me thinking... and here is code that implements a multidimensional indexing subtype of list.
This code doesn't bounds check the dimensions, the user is expected to pass in the correct number of dimensions, with the correct limits on the dimension values. Checks could be added for debugging, but would slow down the code.
I haven't tried to figure out if there is an obvious extension to slicing or ranges, etc. This was sufficient for what I needed last night, to port an algorithm from C.
Oh, and this winds up being much simpler code to understand than what I remember of the code I was looking for. It does use a different syntax: the other code used "list of list" indexing syntax, which is similar to the syntax C uses both for list of list, and for multidimensional arrays. This code uses syntax more reminiscent of multidimensional indexing in Fortran, Cobol, Basic, and other languages.
I'm not sure what syntax NumPy uses, I haven't found a need for NumPy, as yet. But this list will work with elements of any uniform or diverse type, whereas what I read of NumPy it is restricted to a subset of types.