An Array of Challenges #2: Separate a Nested Array

Mathematica, 24 21 bytes

##&@@List/@#0/@#&/@#&

or one of these:

##&@@List/@#&/@#0/@#&
##&@@List@*#0/@#&/@#&
##&@@List/@#&@*#0/@#&

Explanation

The reason this is so short is that it's basically a recursion that doesn't require an explicit base case.

There's a lot of syntactic sugar here, so let's start by ungolfing this. & denotes an unnamed function left of it, whose argument is written as #. Inside this function #0 refers to the function itself, which allows one to write unnamed recursive functions. But let's start by giving the inner function a name and pulling it out:

f[x_] := ##& @@ List /@ f /@ x
f /@ # &

The other important syntactic sugar is f/@x which is short for Map[f, x] i.e. it calls f on every element of x. The reason f[x_] := ... f /@ x doesn't lead to infinite recursion is that mapping something over an atom leaves the atom unchanged without actually calling the function. Hence, we don't need to check for the base case (current element is an integer) explicitly.

So the function f first recurses down to the deepest list inside x, at which point f/@ becomes a no-op. Then we call use ##& @@ List /@ on that. Mapping List over the list simply wraps each element in a separate list, so {1, 2, 3} becomes {{1}, {2}, {3}}. Then we apply ##& to it, which means the head (i.e. the outer list) gets replaced by ##&, so this turns into ##&[{1}, {2}, {3}]. But ##& simply returns it's arguments as a Sequence (which you can think of as an unwrapped list, or a sort of "splat" operator in other languages).

So ##& @@ List /@ turns a list {1, 2, 3} into {1}, {2}, {3} (kind of, that last thing is actually wrapped in the head Sequence, but that vanishes as soon as we use the value anywhere).

That leaves the question why f itself isn't already the solution to the challenge. The problem is that the outermost list should be treated differently. If we have input {{1, 2}, {3, 4}} we want {{1}, {2}, {3}, {4}} and not {{1}}, {{2}}, {{3}}, {{4}}. My original solution fixed this by passing the final result as a list of arguments to Join which would restore the outer level of lists, but this one just skips the outer level by using f itself in a map on the output. Hence f is only applied to the individual elements of the outermost list and never gets to touch that list.

As for the other three solutions, the first one simply applies the recursion outside of f which works just as well. The other two solutions avoid a repeated Map operation by first composing two functions and then mapping the result only once.


J, 19 18 bytes

(<@]/@,~>)S:0 1{::

This is an anonymous verb that takes and returns boxed arrays, which are J's (rather cumbersome) version of nested arrays. See it pass all test cases.

Explanation

This uses the somewhat exotic operations {:: (map) and S: (spread), which operate on boxed arrays. {:: replaces each leaf with the boxed path to that leaf. S: applies a given verb to a given nesting depth, then splats the results into an array.

(<@]/@,~>)S:0 1{::  Input is y.
(        )          Let's look at this verb first.
        >           Open the right argument,
      ,~            append the left argument to it,
    /               then reduce by
 <@]                boxing. This puts the left argument into as many nested boxes
                    as the right argument is long.
                    This verb is applied to y
               {::  and its map
            0 1     at levels 0 and 1.
                    This means that each leaf of y is paired with its path,
                    whose length happens to be the nesting depth of y,
                    and the auxiliary verb is applied to them.
          S:        The results are spread into an array.

Brachylog, 16 bytes

:{##:0&:ga|g}ac|

Try it online!

Explanation

Example input: [1:[2:3]]

:{          }a     Apply the predicate below to each element of the list: [[1]:[[2]:[3]]]
              c    Concatenate: Output = [1:[2]:[3]]
               |   Or: Input = Output = []

  ##                 Input is a list: e.g. Input = [2:3]
    :0&              Call recursively the main predicate with this input: [2:3]
       :ga           Group each element in a list: Output = [[2]:[3]]
          |          Or (not a list): e.g. Input = 1
           g         Group into a list: Output = [1]