4

I want to create an empty NxNxN list in Python. The way I'm doing it now is as follows:

cells = [[[[] for _ in range(N)]
              for _ in range(N)]
              for _ in range(N)]

There must be a better way to write this, the repetition of the "for _ in range(N)" part is a bit horrible.

Any ideas as to how this can be done?

pelegs
  • 317
  • 1
  • 10
  • 3
    if you are tagging this with [multidimensional-array], you might consider using `numpy`. – juanpa.arrivillaga Oct 11 '17 at 16:47
  • 1
    Note that `None` would probably be a better inner element than `[]` for an "empty NxNxN" list. Using `[]` is less efficient and much more prone to accidentally indexing too deep. – user2357112 Oct 11 '17 at 16:49
  • @BrendenPetersen: Not `list`. If you need a nested list, that'd be `np.empty([N, N, N]).tolist()` (which makes lists all the way down, and uses Python scalars instead of NumPy scalars), but keeping the array is probably better. – user2357112 Oct 11 '17 at 16:52
  • 1
    In other words, are you planning to use true multidimensional arrays (which python `list` objects are not) of numerical data, and plan to do numerical calculations with it? Because then you should *definitely* be using `numpy`. On the other hand, if you actually want to use `list` objects, then I would not suggest using `numpy` simply for neat-one-liners. – juanpa.arrivillaga Oct 11 '17 at 16:52
  • @juanpa.arrivillaga: Not at all numerical, this is to be used for a neighbor-cell implementation (i.e. in Molecular Dynamics calculations). Since it will use instances of a self-defined class and not just numbers, numpy seems to be a bit unfitting. – pelegs Oct 11 '17 at 17:39
  • @pelegs yes, totally. Then I suggest sticking with vanilla `list` objects. Unfortunately, other than doing something like `r = range(N)` outside, and doing `[[[[] for _ in r] ...]]` then I don't know what else to suggest – juanpa.arrivillaga Oct 11 '17 at 17:40
  • How big is `N`? – alex Oct 11 '17 at 17:53
  • @alex not more than 1000 – pelegs Oct 12 '17 at 09:10

1 Answers1

2

To avoid manually writing comprehensions for each dimension, here is a recursive approach:

Code

import copy
import itertools as it


def empty(item, n, dim=3):
    """Return a matrix of `n` repeated `item`s."""
    copier = lambda x: [copy.deepcopy(x) for _ in it.repeat(None, n)]
    if not dim:
        return item
    return empty(copier(item), n, dim-1)

Demos

>>> cells = empty([], 3)
>>> cells
[[[[], [], []], [[], [], []], [[], [], []]],
 [[[], [], []], [[], [], []], [[], [], []]],
 [[[], [], []], [[], [], []], [[], [], []]]]

Nested items are separate objects:

>>> cells[2][2][1] = "hi"
>>> cells
[[[[], [], []], [[], [], []], [[], [], []]],
 [[[], [], []], [[], [], []], [[], [], []]],
 [[[], [], []], [[], [], []], [[], 'hi', []]]]

Elements can be any object:

>>> empty("", 2)
[[['', ''], ['', '']], 
 [['', ''], ['', '']]]

Control the final dimension (dim), e.g. N x N, dim=2:

>>> empty("", 2, dim=2)
[['', ''], ['', '']]

Details

empty() is a typical recursive function where the base case returns an item, and the recursive call is made on the copier() function.

# Equivalent
def copier(x):
    return [copy.deepcopy(x) for _ in range(n)]

The latter is similar to the OP's example, but each item is copied in order to return unique objects. Should the item be a nested container, a deepcopy is applied to each element. If unique elements are not critical, you may consider the following implementation for dim=3:

def empty(item, n):
    """Return an (n x n x n) empty matrix."""
    f = ft.partial(it.repeat, times=n)
    return list(map(list, map(f, map(list, map(f, [item]*n)))))
pylang
  • 40,867
  • 14
  • 129
  • 121