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.
精彩评论