开发者

More elegant way to initialize list of duplicated items in Python

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开发者_JAVA技巧 I want to do:

[[[0]]*5]*5

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


After thinking a bit about it, 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 :)


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!-)


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜