0

After reading Automatically growing lists in Python, I'm now attempting to create an automatically growing 2 dimensional list. Basically I would say

>>> l = 2DGrowingList(' ') # Fills the inner lists with space (' ') character
>>> l[1][2] = 'x'
>>> print(l)
[[], [' ', ' ', 'x']]
>>> l[0][1] = 'a'
>>> print(l)
[[' ', 'a'], [' ', ' ', 'x']]

What I attempted was this:

class GrowingList(list):

    def __init__(self, fill):
        super().__init__()
        self.fill = fill

    def __setitem__(self, index, value):
        if index > len(self) - 1:
            self.extend([self.fill] * (index + 1 - len(self)))
        list.__setitem__(self, index, value)

Now I tried to create a new 2D list like so:

l = GrowingList(GrowingList(' '))

But it doesn't work as intented, since it uses the same instance for filling the lists:

>>> l[3][0] = 'x'
>>> print(l)
[[], [], [], ['x']]
>>> l[1][0] = 'y'
>>> print(l)
[['y'], ['y'], ['y'], 'x']

Besides, it doesn't allow me to use l[0][2] = ... instantly, but I must first call l[0] before I can access l[0][2]

Community
  • 1
  • 1
Skamah One
  • 2,456
  • 6
  • 21
  • 31
  • @Aशwiniचhaudhary Not really, even if I used list comprehension, the same instance would still be copied... – Skamah One Mar 16 '14 at 11:53
  • Isn't that obvious, you need to create new instances in the list comprehension. – Ashwini Chaudhary Mar 16 '14 at 11:54
  • @Aशwiniचhaudhary How's that possible, the instance isn't always of the same class with sama attributes? And that doesn't fix the issue where I must set `l[0]` before I can access `l[0][0]` – Skamah One Mar 16 '14 at 11:58
  • Just override `__getitem__` too. (and yes, the rest of the thread is a dup of the linked thread) – roippi Mar 16 '14 at 11:59
  • On a side note: You could just use a `dict` as a potential sparse container for your rows, and possibly the same for your columns... Then when you need lists, just materialise as many rows/columns as needed with suitable defaults... – Jon Clements Mar 16 '14 at 12:03

2 Answers2

0

It would be possible to solve this by changing your class to use a factory function, rather than a fill value. But if you want to do 2D indexing directly, without ever setting anything in the outer list directly, you'll need to make __getitem__ extend the list in addition to __setitem__. That's because l[x][y] tries to get l[x] first, before setting the yth index within it.

A better approach may be to use a dictionary with keys that are row, column tuples. You can use dict.get to check for values and get a default for missing keys.

l = {}
l[3, 5] = "x"
l[0, 0] = "y"
l[9, 9] = "z"
for i in range(10):
    print("".join(l.get((i, j), ".") for j in range(10))) # "." is default value

This prints:

y.........
..........
..........
.....x....
..........
..........
..........
..........
..........
.........z
Blckknght
  • 100,903
  • 11
  • 120
  • 169
0

The easiest solution for your problem might be using a callable tu build the fill value, in order to have different instances in the matrix.

    class GrowingList(list):

    def __init__(self, fill):
        super().__init__()
        self.fill = fill

    def __setitem__(self, index, value):
        if index > len(self) - 1:
            self.extend(self.fill() for i in range(index + 1 - len(self)))
        list.__setitem__(self, index, value)

Note that fill is now a callable, so you need to wrap it inside a function:

l = GrowingList(lambda: GrowingList(lambda: ' '))

You can't rely on the item multiplication idiom either, as it just repeats the value n times. To avoid this, a list comprehension is used.

This approach still has a problem, though:

l = GrowingList(lambda: GrowingList(lambda: ' '))
l[1][3] = 1

IndexError: list index out of range

You may want to consider using a dictionary for a disperse matrix as suggested by Blckknght.

jminuscula
  • 552
  • 2
  • 10