GreatSuccess
GreatSuccess

Reputation: 23

Python generating list with unique lexical elements

Suppose I want to make an empty 3x4 2d array like such.

x = [[0.0]*3]*4

However, using the above code,

print x[0] is x[1]     # Output = True

Meaning,

x[1][1] = 5.0
print x      # Output = [[0.0, 5.0, 0.0], 
             #           [0.0, 5.0, 0.0], 
             #           [0.0, 5.0, 0.0], 
             #           [0.0, 5.0, 0.0]]

To bypass creating this list with 4 identical references to the same list, I've been doing things like:

y = [[0.0]*3, [0.0]*3, [0.0]*3, [0.0]*3]

Where

print y[0] is y[1]     # Output = False
y[1][1] = 5.0
print y      # Output = [[0.0, 0.0, 0.0], 
             #           [0.0, 5.0, 0.0], 
             #           [0.0, 0.0, 0.0], 
             #           [0.0, 0.0, 0.0]]

Another approach would be to use list comprehension

z = [[0.0]*3 for x in range(4)]

but, this still seems a little ugly.

Is there a way to make the array 'y' or 'z' where all the references are unique in an elegant format such as in 'x' using multiply on a list?

Upvotes: 2

Views: 142

Answers (2)

abarnert
abarnert

Reputation: 366133

This is in the official Python Programming FAQ, as How do I create a multidimensional list?.

The FAQ suggests three possibilities:

  1. Create a list of the desired length, then replace each element in a loop.
  2. Use a list comprehension.
  3. Use something like a numpy.array instead of a multidimensional list in the first place.

I think most Python developers would suggest these in the exact opposite order as they're listed in the FAQ… but nobody would complain about any of them. So:

a = np.zeros((3, 4))

a = [[0.0]*3 for _ in range(4)]

a = [None] * 4
for i, _ in enumerate(a):
    a[i] = [0.0]*3

If you find yourself doing this a lot (and not using numpy), you can always wrap it in a function. In fact, this is generally the best way to deal with anything that looks ugly even when you're doing it as pythonically as possible. So:

def make_matrix(r, c):
    return [[0.0]*c for _ in range(r)]

And then it's about as clear as can be:

a = make_matrix(3, 4)

itertools has a function called repeat, which is equivalent to the iterator version of *. The docs show some equivalent pure Python code, so it's pretty easy to adapt it to (shallow or deep) copy the object for each rep:

def repeat(obj, times):
    for _ in range(times):
        yield copy.copy(obj)

list(repeat([0.0]*3, 4))

I don't think this is more readable—especially if you want to use it at multiple levels:

list(repeat(list(repeat(0.0, 3)), 4))

… even if you wrap that in a function that listifies for you:

def srepeat(object, times):
    return list(repeat(object, times))
srepeat(srepeat(0.0, 3), 4)

… and if you wrap it in a function that handles multiple dimensions, you've just re-created make_matrix.

Upvotes: 3

user2357112
user2357112

Reputation: 282026

List comprehensions are the usual answer. Not as clean as sequence multiplication, but the best that's available:

[[0.0]*3 for _ in xrange(4)]

Note that for a lot of applications where you want a grid of values, you're doing something that numpy ndarrays could do faster and cleaner.

Upvotes: 4

Related Questions