Mathematica, 16 14 bytes
{##&@@#&//@#}&
An unnamed function which takes and returns a list, e.g.:
{##&@@#&//@#}& @ {{{20}, {"Hi"}, "Hi", 20}}
(* {20, "Hi", "Hi", 20} *)
Explanation
Syntactic sugar party!
To understand how this works, note that every expression in Mathematica is either an atom (e.g. numbers, strings, symbols) or a compound expression of the form f[a, b, c, ...]
, where f
, a
, b
, c
are themselves arbitrary expressions. Here, f
is called the head of the expression. Everything else on top of that is just syntactic sugar. E.g. {a, b, c}
is just List[a, b, c]
.
We start with //@
which maps a functions over all levels of a list. For instance:
f //@ {{{20}, {"Hi"}, "Hi", 20}}
(* f[{f[{f[{f[20]}], f[{f["Hi"]}], f["Hi"], f[20]}]}] *)
Note that this maps f
over atoms as well as compound expressions. What we're now looking for is a way to get rid of the list heads and keep everything else.
The Apply
function is normally used to feed the elements of a list as separate arguments to a function, but its actual definition is more general and simply replaces the head of an expression. E.g. Apply[g, f[a, b]]
gives g[a, b]
.
Now there's a special "head" called Sequence
that simply vanishes. E.g. {a, Sequence[b, c], d}
just evaluates to {a, b, c, d}
. The idea for flattening the list is to replace the heads of all inner lists with Sequence
so that they get splatted into their surrounding list. So what we want is to Apply
the head Sequence
to the lists. Conveniently if we Apply
something to an atom, it just leaves the atom unchanged, so we don't have to distinguish between types of expressions at all.
Finally, there's one small issue: f
is also applied to the outermost level, so that it also removes the outermost List
, which we don't want. The shortest way to counter that is simply to wrap the result in a list again, such that the surrounding Sequence
can safely vanish.
Note that there's neither Apply
nor Sequence
in the code. @@
is an operator form of Apply
and ##&
is a standard golfing trick to shorten the long built-in name Sequence
. So ungolfing everything a bit, we get something like:
flatten[list_] := { MapAll[Apply[Sequence], list] }
For more details on how and why the ##&
works, see the section on "Sequences of arguments" in my answer for the Mathematica tips.
unflatten
question but noflatten
question on PPCG. \$\endgroup\$["[",[["[",],'[',"['['"]]
as a test case. \$\endgroup\$'
and"
as delimiters. (But I agree that a test case involving[
,]
,"
and\
inside a string would be useful.) \$\endgroup\$