views:

173

answers:

4

If I want a list initialized to 5 zeroes, that's very nice and easy:

[0] * 5

However if I change my code to put a more complicated data structure, like a list of zeroes:

[[0]] * 5

will not work as intended, since it'll be 10 copies of the same list. I have to do:

[[0] for i in xrange(5)]

that feels bulky and uses a variable so sometimes I even do:

[[0] for _ in "     "]

But then if i want a list of lists of zeros it gets uglier:

[[[0] for _ in "     "] for _ in "     "]

all this instead of what I want to do:

[[[0]]*5]*5

Has anyone found an elegant way to deal with this "problem"?

A: 

One solution is to have a helper function:

import copy
def r(i,n):
    return [copy.deepcopy(i) for _ in xrange(n)]

then:

r([0],5)
r(r([0],5),5)

But this syntax is ugly.

Claudiu
+2  A: 

Another is to extend the list class:

import copy
class mlist(list):
  def __mul__(self, n):
    res = mlist()
    for _ in xrange(n):
      for l in self:
    res.append(copy.deepcopy(l))
  return res

then:

>>> hey = mlist([mlist([0])])
>>> hey
[[0]]
>>> hey * 4
[[0], [0], [0], [0]]
>>> blah = hey * 4
>>> blah[0][0] = 9
>>> blah
[[9], [0], [0], [0]]

but initializing the mlist is annoying.

Claudiu
+5  A: 

If I had a frequent requirement for lists of lists of lists of ... I'd simply package the building thereof into a small factory function, such as:

import copy

def multi_dimension_list(baseitem, *dimensions):
  dimensions = list(dimensions)
  result = [baseitem] * dimensions.pop(-1)
  for d in reversed(dimensions):
    result = [copy.deepcopy(result) for _ in range(d)]
  return result

eg = multi_dimension_list(0, 3, 4, 5)
print(eg)
# and just to prove the parts are independent...:
eg[1][1][1] = 23
print(eg)

In practice, I don't even bother, because my uses of multi-dimensional lists of this kind are few and far between, so that inline list comprehensions are just fine. However, the general idea of building up your own module of little utility functions for simple tasks that you do need to perform often and (in your opinion) aren't done elegantly by inline idioms, is really the only way to go!-)

Alex Martelli
I run this function and meet a problem. The eg[1][1][1] assignment operation should only change the element is position eg[1][1][1]. But I find that it changes the value of three elements. It is wired...
chnet
Right -- it needs a deepcopy (editing to fix).
Alex Martelli
The only times I needed for multi-dimensional lists was when I wanted matrices. So I just used numpy instead.
THC4k
@THC4k, yep, that's no doubt a large part of why I find myself using multi-dimensional lists pretty rarely, too.
Alex Martelli
+7  A: 

I had to thought about it a little bit, but then I came up with this solution: (7 lines without import)

# helper
def cl(n, func):
    # return a lambda, that returns a list, where func(tion) is called
    return (lambda: [func() for _ in range(n)])

def matrix(base, *ns):
    # the grid lambda (at the start it returns the base-element)
    grid = lambda: base

    # traverse reversed, to handle the midmost values first
    for n in reversed(ns):
        # assign a new lambda with the last grid within (and call it)
        grid = cl(n, grid)

    return grid() # call the full grid (but the matrix calls you ^^)

The tests give the following results:

>>> from pprint import pprint as pp
>>> 
>>> matrix(None, 2,3)
[[None, None, None], [None, None, None]]
>>> 
>>> matrix(None, 4,3)
[[None, None, None], [None, None, None], [None, None, None], [None, None, None]]
>>> 
>>> x = matrix(None, 3,5,2)
>>> pp(x)
[[[None, None], [None, None], [None, None], [None, None], [None, None]],
 [[None, None], [None, None], [None, None], [None, None], [None, None]],
 [[None, None], [None, None], [None, None], [None, None], [None, None]]]
>>> x[1][3][0] = "test"
>>> pp(x)
[[[None, None], [None, None], [None, None], [None, None], [None, None]],
 [[None, None], [None, None], [None, None], ['test', None], [None, None]],
 [[None, None], [None, None], [None, None], [None, None], [None, None]]]

Another solution, which has the advantage of using the "[[[0]]*5]*5"-syntax:

def uniq(base, l):
    # function used to replace all values with the base
    nl = []
    for i in l:
        if type(i) is list:
            nl.append(uniq(base, i)) # recursion for deep lists
        else:
            nl.append(base)
    return nl

Test:

# first arg is the base, the 0 inside the [] is just a dummy
# (for what None is the best choice usually)
>>> x = uniq(0, [[[0]]*5]*5)
>>> x[0][3][0] = 5
>>> pp(x)
[[[0], [0], [0], [5], [0]],
 [[0], [0], [0], [0], [0]],
 [[0], [0], [0], [0], [0]],
 [[0], [0], [0], [0], [0]],
 [[0], [0], [0], [0], [0]]]

btw. the numpy library has a np.zeros(s)-function, where s is a shape like (3,4,5)

>>> s = (2,2)
>>> np.zeros(s)
array([[ 0.,  0.],
       [ 0.,  0.]])

Finally a performance test:

# functions are already defined ...
import timeit
>>> # Alex Martelli's Code
>>> t1 = timeit.Timer( lambda: multi_dimension_list(None, 3,4,5) )
>>> # the two mentioned above
>>> t2 = timeit.Timer( lambda: matrix(None, 3,4,5) )
>>> t3 = timeit.Timer( lambda: uniq(None, [[[None]*5]*4]*3) )
>>> 
>>> t1.timeit(10000)
2.1910018920898438
>>> t2.timeit(10000)
0.44953203201293945
>>> t3.timeit(10000)
0.48807907104492188

I found it really interesting to discover this problem. So, thanks for the question :)

Joschua
oo cute 15charararar
Claudiu
Claudiu, I added a new method and some details
Joschua
why do you not accept one of these answers?
Joschua
@Joschua: sorry, forgot! this one prob has the best answer, which is to use numpy
Claudiu