Can I efficiently do multidimensional slicing?

Let’s say I have a Python list L of arbitrary dimensions and of known shape shape: List[int].

I’d like to slice this list multidimensionally having one interval and one step for each dimension. Note that my task is developing an efficient algorithm without using builtin Python slices (L[start:end:step] notation, or any other Python-exclusive trick), just being able to access list elements one at time.

L = [[[1, 2, 3],
      [4, 5, 6]],
     [[7, 8, 9],
      [10, 11, 12]]]

slices = [(0, 2, 2),
          (0, 0, 1),
          (0, 1, 1)]

result = some_magic_algorithm_i_cant_find(L, slices)
print(result)

# [[[1, 3]],
#  [[7, 9]]]

Note that in my example L has three dimensions, but I’d like my algorithm to be as general as possible, hence offering support for an arbitrary number of dimensions.

Answer

Recursive (Try it online!):

def some_magic_algorithm_i_cant_find(L, slices):
    if not slices:
        return L
    a, b, c = slices[-1]
    return [some_magic_algorithm_i_cant_find(L[i], slices[:-1])
            for i in range(a, b+1, c)]

If you hadn’t unpythonically made the end indexes inclusive, it would be simpler (Try it online!):

def some_magic_algorithm_i_cant_find(L, slices):
    if not slices:
        return L
    return [some_magic_algorithm_i_cant_find(L[i], slices[:-1])
            for i in range(*slices[-1])]

Edit: Since I was kinda challenged to a duel, here’s my similarly optimized version, seems about 10% faster than blhsing’s:

def donttalkjustcode2(L, slices):
    *rest, (start, stop, step) = slices
    slice = L[start : stop+1 : step]
    if rest:
        return [donttalkjustcode2(sublist, rest)
                for sublist in slice]
    return slice

Edit 2: Interesting… I replaced my list comp to be closer to blhsing’s, and against my expectation, it became even faster:

def donttalkjustcode3(L, slices):
    *rest, (start, stop, step) = slices
    slice = L[start : stop+1 : step]
    if rest:
        sliced = []
        for sublist in slice:
            sliced.append(donttalkjustcode3(sublist, rest))
        return sliced
    return slice

Benchmark results:

4.566 μs  donttalkjustcode
3.043 μs  donttalkjustcode2
2.525 μs  donttalkjustcode3
3.405 μs  blhsing

Try the benchmark online!