# Transpose a multidimensional array

Transposition is an operation on 2-dimensional arrays that flips every element across the main diagonal:

[[1,2,3],    [[1,4],
[4,5,6]] ->  [2,5],
[3,6]]


If we call the above array m, then the 2 is the second item of the first row of m, or m[0][1]. The 2 can be thought of as having a coordinate of [0, 1] in the array because of this. When the matrix is transposed, the 2 moves to the first item of the second row, or m[1][0], and its coordinate is now [1, 0].

In general, tranposition can be described as taking the coordinate of every item in the array and swapping the first and second item. This can be generalised to higher dimensions by taking the coordinate rotating it left by one item - moving the first item of the coordinate to the end.

This idea of a coordinate extends fairly easily to arrays of higher dimensions. For example, with the array z = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]], the 6 is z[1][0][1], and its coordinate is [1, 0, 1].

If we apply the same idea of transposition to this coordinate, the coordinate of 6 [1, 0, 1] is shifted left by one item to result in [0, 1, 1], meaning it is now z[0][1][1]. Similarly, the 2 with coordinate [0, 0, 1] is rotated left to result in [0, 1, 0]If we apply this operation to every element of the array, we get [[[1, 5], [2, 6]], [[3, 7], [4, 8]]].

Your challenge is to apply this transformation to a multidimensional rectangular array of positive integers.

You may additionally take the dimensions of the array.

## Testcases

[[1]] -> [[1]]
[[[[1, 2]]]] -> [[[[1], [2]]]]
[[1, 2, 3], [4, 5, 6]] -> [[1, 4], [2, 5], [3, 6]]
[[[1, 2], [3, 4]], [[5, 6], [7, 8]]] -> [[[1, 5], [2, 6]], [[3, 7], [4, 8]]]
[[[[9]], [[10]]]] -> [[[[9]]], [[[10]]]]
[[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]] -> [[[1, 7], [2, 8], [3, 9]], [[4, 10], [5, 11], [6, 12]]]

• Can I take the dimension as an additional input? Commented Oct 5, 2023 at 5:50
• @CommandMaster Yes, and I believe that's in I/O defaults somewhere Commented Oct 5, 2023 at 6:12
• The only relevant default I can find is A multidimensional rectangular array may be represented by the list [dimensions, flattened array] Commented Oct 5, 2023 at 6:23
• It would be nice if you used an example other than [1,0,1], you say transposition can be either swapping the first two or rotating the entire thing, and [1,0,1] has the same result for both. It takes a very careful reading to figure out which of the two ideas of transposition you are using. Commented Oct 5, 2023 at 12:49
• @CommandMaster That input format does trivialise the problem though, it takes 26 bytes in Charcoal as opposed to 42 for a multidimensional array.
– Neil
Commented Oct 7, 2023 at 7:21

# Python 3, 51 bytes

f=lambda a,b:b<2and[*a]or[f(x,b-1)for x in zip(*a)]


Try it online!

Takes as input the list and then the dimension.

If we look at the list of dimensions, a 2D transpose is equivalent to swapping the first two, so if we recursively do that we swap dimensions 0,1, then 1,2, and so on, so we move the first dimension to the last position.

If the output can contain have tuples in the topmost level it can be -1 by replacing and[*a]or with and a or.

# Python 3 + numpy, 37 36 bytes

-1 thanks to @loopwalt

lambda a,b:a.transpose(range(1-b,1))


Try it online!

Takes a numpy array and the dimension as input.

• range(1-b,1) saves a byte, I believe, because it doesn't need splatting. Commented Oct 5, 2023 at 18:12

# R, 23 bytes

\(x,d)aperm(x,1:d%%d+1)


Attempt This Online!

A situation where R has a built-in that meets the requirements! A function that takes a multidimensional array and the number of dimensions as its two parameters and returns a multidimensional array with the coordinates transposed left one.

Note that in the printout R loops through dimensions that are 3 or above, printing a 2d matrix for each 2d plane. Within that matrix, rows are dimension 1 and columns are dimension 2.

Thanks to @Giuseppe for suggesting a fix that works for 1-dimensional arrays.

• I suggest 1:d%%d+1 which is the same length but doesn't fail on a dim-1 input. Commented Oct 5, 2023 at 13:41
• Thanks @Giuseppe I’d assumed that multi-dimensional implied more than 1, but agree it’s good that it generalises to 1d arrays too. Commented Oct 5, 2023 at 14:54

# Uiua, 1 byte

⍉


Try it!

(When reading the output, dashes on the left indicate the rank of the array and whitespace separates axes.)

• Also works in BQN, which I assume is the inspiration for the Uiua builtin. Commented Oct 5, 2023 at 21:13
• Why is this 3 bytes? I understand that the symbol ⍉ is conventionally encoded in 3 bytes, but Uiua seems only to have 54 glyphs (at least visible on the online interpreter), which combined with a-z,A-Z & 0-9, space & newline comes to 118, so each character should be recodable into a single byte...? Commented Oct 10, 2023 at 12:01
• @DominicvanEssen To my understanding, it's not enough that it could be encoded. An implementation has to exist. Commented Oct 10, 2023 at 14:00
• Oh, I see. Like this, then, right? That seems sufficiently straightforward that it could almost be worthwhile to just write it... Edit: or maybe golfing a reduced-byte-character-system encoder for any language should be a challenge, if it doesn't already exist... Commented Oct 10, 2023 at 14:04
• I really don't think that's a problem: many/most SBCS-encodable golfing languages (Husk, Vyxal, ...) also allow multi-byte characters that aren't in their encoding. If you choose to use them in your code, you simply lose the advantage of the SBCS-encoding (since they aren't encoded). Commented Oct 10, 2023 at 15:41

# Nekomata, 6 bytes

ʷ∑→ᵉbD


Attempt This Online!

ʷ∑→ᵉbD          Take [[[1,2],[3,4]],[[5,6],[7,8]]] as an example
ʷ∑          Sum of the nested array
[[[1,2],[3,4]],[[5,6],[7,8]]] -> 36
→         Increment
36 -> 37
ᵉb       Convert the input from a list of digits in that base
[[[1,2],[3,4]],[[5,6],[7,8]]], 37 -> [[42,80],[118,156]]
D      Convert each number back to a list of digits in that base
[[42,80],[118,156]], 37 -> [[[1,5],[2,6]],[[3,7],[4,8]]]


This works because b and D are vectorized in different ways.

b (\fromBase) is not vectorized by itself. It simply views the input as a list of digits in that base. However, when converting the list of digits to a number, the addition and multiplication are vectorized. For example [[[1,2],[3,4]],[[5,6],[7,8]]] 37 b is [[1,2],[3,4]]*37+[[5,6],[7,8]]=[[42,80],[118,156]].

D (\toBase) is vectorized in the usual way. It converts each number in the input to a list of digits in that base. For example [[42,80],[118,156]] 37 D converts each number (42,80,118,156) to a list of digits in base 37, which is [[[1,5],[2,6]],[[3,7],[4,8]]].

• Could you please explain how this works? Commented Oct 11, 2023 at 4:00

# Python 2, 33 bytes

f=lambda*L:L*(L<(f,))or map(f,*L)


Attempt This Online!

Ports @Command Masters Python 3 (non numpy). Takes splatted input. Innermost lists will be tuples.

# 05AB1E, 21 bytes

JΔ€}\N<Ý®K"€"×'ø«J.V


Explanation:

J         # Join each inner-most list together to a single item
Δ€}\N<  # Then determine the depth-2 of this multidimensional list
Δ  }     #  Loop until it no longer changes:
€      #   Flatten it one level down
\    #  After the changes-loop, discard the resulting flattened list
N   #  Push the last 0-based index
<  #  Decrease it by 1
Ý         # Push a list in the range [0,depth-2]
®K       # Remove -1 so [0,-1] becomes [0]
"€"×      # Convert each value in this list to that many "€" as string
'ø«  '# Append an "ø" to each string
J  # Join this list of strings together
.V        # Evaluate and execute it as 05AB1E code on the (implicit) input
# (after which the result is output implicitly)


Try all test cases without the .V to see which maps/transpose builtins are actually being generated by JΔ€}\N<Ý®K"€"×'ø«J, where € is a map (and therefore €€ a nested map) and ø transposes a matrix/2D-list, swapping its rows/columns.

Minor notes:

1. Using Δ€}\NÍ (where Í is -2) only seems to work for the last test case: try it online; and with < but without the leading J it works for some of the test cases, but not the last two: try it online; so using a leading J and > works for all test cases.
2. The range builtin Ý also accepts negative values for its $$\[0,n]\$$-ranged list. So for singular lists, it'll result in [0,-1] after both the J and Δ€}\N<, for which the -1 are ignored by the × and it basically evaluates ø-1ø: try it online.
3. Why use "€" for string € but 'ø for string ø? Because ' is also used for pushing a dictionary word, and '€× is apparently the dictionary word view: try it online; whereas 'ø« is coincidentally none: try it online.

# Charcoal, 42 bytes

⊞υθＦυ«≔Ｅ⌊ιＥι§μληＷι≔⊟ικＦη⊞ικＦη¿⁺⟦⟧⌊κ⊞υκ»⭆¹θ


Try it online! Link is to verbose version of code. Explanation: Port of @CommandMaster's Python 3 answer.

⊞υθ


Start by processing the input array.

Ｆυ«


Loop over the arrays that need to be transposed.

≔Ｅ⌊ιＥι§μλη


Transpose the top two dimensions of the current array.

Ｗι≔⊟ικＦη⊞ικ


Replace the contents of the original array with that of the transposed array, thus transposing the array in-place.

Ｆη¿⁺⟦⟧⌊κ⊞υκ


If the elements of the transposed arrays are themselves arrays rather than lists of numbers, then push those arrays so that they get transposed in-place.

»⭆¹θ


Pretty-print the final state of the original array.

41 bytes using the newer version of Charcoal on ATO:

Ｗ⁺⟦⟧ΣΣθ«⊞υＬ⌊⌊θＵＭθΣκ»≔Ｅ⌊θＥθ§λκθＦ⮌υ≔⪪θιθ⭆¹θ


Attempt This Online! Link is to verbose version of code. Explanation:

Ｗ⁺⟦⟧ΣΣθ«


Until the input array has two dimensions, ...

⊞υＬ⌊⌊θ


... record the number of elements in the third dimension, and...

ＵＭθΣκ


... flatten the third dimension into the second dimension.

»≔Ｅ⌊θＥθ§λκθ


Transpose the array.

Ｆ⮌υ


Loop over the sizes of the dimensions.

≔⪪θιθ


Unflatten the array.

⭆¹θ


Pretty-print the final array.

32 bytes by taking the dimensions of the array as a second argument:

Ｆ✂η²ＵＭθΣκ≔Ｅ⌊θＥθ§λκθＦ⮌✂η²≔⪪θιθ⭆¹θ


Attempt This Online! Link is to verbose version of code. Explanation: As above, but using the input dimensions, ignoring the first two which aren't needed.

26 bytes by taking a list of dimensions and a flattened array as I/O:

Ｆ…θ¹Ｉ⟦⊞ＯΦθλιＥη§§⪪η÷Ｌηιλ÷λι


Try it online! Link is to verbose version of code. Explanation:

  θ                         Input dimensions
…                          Truncated to length
¹                        Literal integer 1
Ｆ                           Loop over dimension
θ                  Input dimensions
Φ                   Filtered where
λ                 Not first dimension
⊞Ｏ                    Concatenated with
ι                First dimension
η              Input array
Ｅ               Map over elements
η          Input array
⪪           Split into arrays of length
η       Input array
Ｌ        Length
÷         Integer divided by
ι      First dimension
§            Cyclically indexed by
λ     Current index
§             Indexed by
λ   Current index
÷    Integer divided by
ι  First dimension
⟦                      Separate dimensions from elements
Ｉ                       Implicitly print


Ｆ…θ¹ saves a byte over ≔§θ⁰ι and two bytes over repeating §θ⁰ instead of ι.

(Length of array could also be product of dimensions of course.)

# Wolfram Language (Mathematica), 13 bytes

#0/@Thread@#&


Try it online!

Input [array]. Recursively Threads the first dimension through all other dimensions. Thread behaves more nicely than \[Transpose] on arrays of dimension <2.

## Wolfram Language (Mathematica), 23 bytes

D'[[0]][,{##},Listable]


Try it online!

Taking a rare chance to showcase Function's 3-argument form, which lets us set attributes on a pure function, even if this ends up being almost twice the length of the shortest solution.

Input [array...]. Append @@#& to take [array] input.

A Listable function that enlists its arguments. D'[[0]] is slightly shorter than writing out Function.

# Julia, 37 bytes

~=ndims
!x=permutedims(x,[~x;1:~x-1])


Attempt This Online!