1

I have searched and found these questions: How to create a multi-dimensional list and N dimensional array in python which hint toward what I am looking for, but they seem to only make 2D arrays and not ND arrays.

Problem:

My problem is that I am able to create an n-dimensional list of lists for a known n, but I am not sure how it can be generalized to work with all values of n.

Consider this example:

def makeList(n):
    return [[[n for _ in range(n)] 
                for _ in range(n)] 
                for _ in range(n)]
print(makeList(3))

Output:

[[[3, 3, 3], 
  [3, 3, 3], 
  [3, 3, 3]], 
 [[3, 3, 3], 
  [3, 3, 3], 
  [3, 3, 3]], 
 [[3, 3, 3], 
  [3, 3, 3], 
  [3, 3, 3]]]

This will create a list of lists that is a 3x3x3 array of 3's which is the intended result, but if we use a different n:

print(makeList(2))

Output:

[[[2, 2], 
  [2, 2]], 
 [[2, 2], 
  [2, 2]]]

This will create a list of lists that is a 2x2x0 array of 2's and this is not the intended result. Instead the results should be a list of lists that is a 2x2 array of 2's.

Likewise if we set n = 4:

print(makeList(4))

This will give a list of lists that is 4x4x4 when it should give a 4x4x4x4 list of lists.

The main issue is that the number of for loops must change depending on the input, but obviously the code can't "come to life" and recode itself magically, hence my issue.

I am looking for a way to get this result that is simple. I am sure I could continue developing ideas for solutions, but I have not been able to think of anything that is concise.

What I have tried:

The first idea I thought of was to use recursion, and this my simple approach:

def makeList(n, output = []):
    if not output:
        output = [n for _ in range(n)]
    else:
        output = [output for _ in range(n)]
    if len(output) == n:
        return output
    else:
        return makeList(n, output)

This obviously will not work because if len(output) == n: will execute the first iteration since the length of the inner loops is equal to the length of the outermost loop. However, even if there was a condition that properly terminated the function, this solution would still not be ideal because I could run into maximum recursion errors with large values of n. Furthermore, even if these issues were resolved this code is still quite long as well as time consuming.

The other potential perspective I considered (and the solution that I found that works) is using a dict. My solution saves the intermediate lists of lists in a dict so it can be used for the next iteration:

def makeList(n):
    d = {str(i): None for i in range(n)}
    for i in range(n):
        if i == 0:
            d[str(i)] = [n for _ in range(n)]
        else:
            d[str(i)] = [d[str(i-1)] for _ in range(n)]
    return d[str(n-1)]

But again, this is very long and doesn't seem very pythonic.

Solution requirements:

The ideal solution would be much more concise than mine with an efficient time complexity that only uses built-in functions.

Other options that are close to these requirements would also be helpful as long as the spirit of the answer is trying to best meet the requirements.

Being concise is the most important aspect, however, which is why I am not fully happy with my current attempts.

Eli Harold
  • 2,280
  • 1
  • 3
  • 22
  • "The ideal solution would be 2-3 lines " this is a silly requirement. StackOverflow is *not for code golf* – juanpa.arrivillaga Feb 08 '22 at 21:54
  • @juanpa.arrivillaga it's for readability as I have quite a few different places where I need this type of functionality (slightly different set up and use) so repeating the 7-8 line solution every time would make for messy code. I think that is very reasonable. – Eli Harold Feb 08 '22 at 21:57
  • 1
    That doesn't make any sense. The way to improve readability is to *wrap that in a function and re-use that function*. No, this is not a reasonable requirement. How do you think all the `numpy` functions work? – juanpa.arrivillaga Feb 08 '22 at 21:58
  • @juanpa.arrivillaga true I could do that, but I much prefer `return np.full(tuple(range(n)), n)` it's a lot cleaner and less work. – Eli Harold Feb 08 '22 at 22:02
  • 1
    Well, *right* because the *function is already written for you*. – juanpa.arrivillaga Feb 08 '22 at 22:03
  • @juanpa.arrivillaga My solution was "already written" as well.......... – Eli Harold Feb 09 '22 at 13:07

5 Answers5

3

Using tuple(n for _ in range(n)) to get the dimension that you want, and numpy to generate a multidimensional array:

import numpy as np

def make_array(n):
    return np.full(tuple(n for _ in range(n)), n)

for n in range(1,5):
    print(make_array(n))

Output:

[1]

[[2 2]
 [2 2]]

[[[3 3 3]
  [3 3 3]
  [3 3 3]]

 [[3 3 3]
  [3 3 3]
  [3 3 3]]

 [[3 3 3]
  [3 3 3]
  [3 3 3]]]

[[[[4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]]

  [[4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]]

  [[4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]]

  [[4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]]]


 [[[4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]]

  [[4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]]

  [[4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]]

  [[4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]]]


 [[[4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]]

  [[4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]]

  [[4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]]

  [[4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]]]


 [[[4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]]

  [[4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]]

  [[4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]]

  [[4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]
   [4 4 4 4]]]]
Stef
  • 13,242
  • 2
  • 17
  • 28
2

Your BEST plan is to use numpy for this. You can create an arbitrary array of all zeros with np.zeros( (4,4,4) ), or an array of 1s with np.ones( (4,4,4) ). If you're going to be working with arrays very much at all, you will certainly want to use numpy.

Tim Roberts
  • 48,973
  • 4
  • 21
  • 30
1

I've sketched out a recursive function that might suit:

def nd_list(n, x=None):
    if x is None:
        x = n
    if x == 1:
        return [n] * n
    return [nd_list(n, x-1)] * n

In two lines using no external libraries!

def nd_list(x, z):
    return [z] * z if x == 1 else [nd_list(x-1, z)] * z

In two lines (just) and without the list of lists surprising behaviour:

def nd_list(x, z):
    return [0 for _ in range(z)] if x == 1 else [nd_list(x-1, z) for _ in range(z)]
Jack Deeth
  • 3,062
  • 3
  • 24
  • 39
  • This is an improvement to my recursive function, but still not quite as concise as I would like. – Eli Harold Feb 08 '22 at 21:51
  • 3
    This will probably lead to undesired behaviour if you attempt to modify the resulting array. See also [List of lists changes reflected across sublists unexpectedly](https://stackoverflow.com/questions/240178/list-of-lists-changes-reflected-across-sublists-unexpectedly) – Stef Feb 08 '22 at 22:10
  • @Jack Deeth I like the updated versions! Good find. – Eli Harold Feb 10 '22 at 15:07
1

I mean, I like numpy and all, but if I want to do general dynamic programming I don't want to have to pip install a massive library. Would also suck hard for technical interviews requiring complex DP.

Here's a simple, readable function that takes advantage of the built-in itertools module in Python (it uses repeat) and the copy module for making deep copies of lists (otherwise, you get that "surprising" list behavior where modifying one entry modifies many). It's also pretty sane in terms of the API: you can call it with a tuple of dimensions (like numpy's array's shape field):

from itertools import repeat
from copy import deepcopy

def ndarray(shape, val=None):
    base = val
    for dim in reversed(shape):
        base = list(map(deepcopy, repeat(base, times=dim)))
    return base

This is what is made:

>>> import pprint
>>> pprint.pprint(ndarray((2, 3, 4), val=1))
[[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]],
 [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]]

For your original question, you could easily wrap makeList(n) around this:

def makeList(n, val=None):
    return ndarray([n]*n, val=val)
a-la-linuques
  • 71
  • 1
  • 5
0

Using python support for pattern matching

def nlist(shape: tuple[int, ...], fill_with=None):
    match shape:
        case (n,):
            return [fill_with] * n
        case (n, *rest):
            return [nlist(rest, fill_with) for _ in range(n)]
        case _:
            raise ValueError(f'Invalid value shape={shape}')


def makeList(n):
    return nlist((n,)*n, n)
frndmg
  • 71
  • 1
  • 5