12

I have a list as follows

lis= {a, {{{b}, {c,d,e}}, {{f}, {g,h,i}}}}

which I would like to flatten to

flattenLis={a,{b,c,d,e},{f,g,h,i}}

Does anyone have a hint. All my flatten attempts did not work. Thanks

Kuba
  • 136,707
  • 13
  • 279
  • 740
Phadreus
  • 461
  • 2
  • 10

6 Answers6

12

Here is an approach where I apply the flattening "from the bottom up" (negative level specification). This works by operating on {lis} instead of lis, in order to have sufficiently many levels even when there's no list wrapping an element, such as a:

Flatten[Map[Flatten, {lis}, {-3}], 2]

(* ==> {a, {b, c, d, e}, {f, g, h, i}} *)

My approach also works for this example:

lis2 = {a, {{{b}, {c, d, e}}, {{f}, {g, h, i}}}, b};

Flatten[Map[Flatten, {lis2}, {-3}], 2]

(* ==> {a, {b, c, d, e}, {f, g, h, i}, b} *)
Jens
  • 97,245
  • 7
  • 213
  • 499
  • According your idea, {#1, Sequence @@ #2} & @@ Map[Flatten, lis, {-3}] also works:) – xyz Mar 24 '15 at 01:39
  • There is a potential problem with this approach. This will fail if the elements are not atomic, e.g. if b = Sqrt[2];. My method does not fail in that circumstance, nor does it fail on lis2 despite your assertion. – Mr.Wizard Mar 24 '15 at 03:46
  • @Mr.Wizard Yes, the bottom-up approach can only work if the leafs are atomic - I guess maybe one could ensure that using Block if the entries are at least named by symbols that can be blocked. – Jens Mar 24 '15 at 04:19
  • 1
    @ShutaoTang Yes, but your Slot based approach wouldn't work with my lis2. Anyway, at this point the OP certainly has a lot of methods to choose from... – Jens Mar 24 '15 at 04:31
10

Not sure how general it will be:

{#, ## & @@ Flatten /@ #2} & @@ lis
{a, {b, c, d, e}, {f, g, h, i}}
Kuba
  • 136,707
  • 13
  • 279
  • 740
8

One approach:

MapAt[Flatten, lis, {2, All}] ~FlattenAt~ 2
{a, {b, c, d, e}, {f, g, h, i}}

Hopefully some combination of MapAt, Flatten, and FlattenAt will work for any structure you have.

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • This works for the example, but not for lis = {a, {{{b}, {c, d, e}}, {{f}, {g, h, i}}}, b}. I upvoted anyway, since the question doesn't specify what generalizations are desirable. – Jens Mar 23 '15 at 22:49
  • I discovered that this solution was restricted in V8.MapAt::psl: "Position specification {2,All} in MapAt[Flatten,{a,{{{b},{c,d,e}},{{f},{g,h,i}}}},{2,All}] is not an integer or a list of integers. " – xyz Mar 24 '15 at 01:26
  • @ShutaoTang That is correct; support for All and Span within MapAt was (silently) introduced in version 9; see: (31173). There are alternatives in that Q&A that will work in earlier versions. – Mr.Wizard Mar 24 '15 at 03:39
  • @Jens I'm not sure what you mean; using your lis I get {a, {b, c, d, e}, {f, g, h, i}, b} -- what else do you want? – Mr.Wizard Mar 24 '15 at 03:40
  • @Mr.Wizard Oh, I know what happened: I was on MMA version 8, and tested my lis2 with your method. It errored out, but now I see it works in version 10. In version 8, your answer doesn't even work with the original lis... It's because MapAt in version 8 doesn't seem to accept position specification All. That change isn't mentioned in the version-10 documentation. – Jens Mar 24 '15 at 04:12
7

Also:

lis = {a, {{{b}, {c, d, e}}, {{f}, {g, h, i}}}};
Fold[Apply[## &, #, {#2}] &, lis, {1, 2}]
(* {a, {b, c, d, e}, {f, g, h, i}} *)

And ... ♯ = {## & @@ #, ## & @@@ #2} & @@ ({##}) &'s close relative:

♭ = ## & @@@ (## & @@@ {## & @@@ # & /@ #} & /@ #) &;

Examples:

♭ @ lis
(* {a, {b, c, d, e}, {f, g, h, i}} *)

lis2 = {a, {{{b}, {c, d, e}}, {{f}, {g, h, i}, {x}}}, b};
♭ @ lis2
{a, {b, c, d, e}, {f, g, h, i, x}, b}
kglr
  • 394,356
  • 18
  • 477
  • 896
  • 1
    Your last one is probably a quote from the pirate in Asterix&Obelix, right? But it doesn't work on lis = {a, {{{b}, {c, d, e}}, {{f}, {g, h, i}}}, b}. – Jens Mar 23 '15 at 22:40
  • @Jens, right! and Getafix says there is a fix:) – kglr Mar 23 '15 at 23:17
  • If anyone looks at the edit history, they will get dizzy... I could still annoy you with this example, though: lis = {a, {{{b}, {c, d, e}}, {{f}, {g, h, i}, {x}}}, b}; where I added an {x} together with the {f}. Your first approach works, the second still doesn't. – Jens Mar 23 '15 at 23:22
  • @Jens, deleted the quotes from the pirate and getafix:) Actually, i am surprised the first one works beyond op's simple example. – kglr Mar 23 '15 at 23:28
  • I didn't really intend to make you delete the other solution - it was fun to look at... – Jens Mar 24 '15 at 16:53
  • @jens, don't worry. The reason i deleted was i was getting dizzy myself; and (although I think the modified version does handle your second example) i wasn't quite sure if it would be able to handle the upcoming one:) I had another just-as-silly "Look Ma! No letters!" function in another q/a with \[Sharp] (\[Flat]s cousin) but i cannot find it now. – kglr Mar 24 '15 at 18:39
6

Another approach is rule replacement, which can be restricted to a specific level.

In[16]:=Replace[lis, l_List :> Sequence @@ Flatten[l], {1}] === flattenList

Out[16]=True

I didn't see an easy way to make this one work without Sequence.

Pillsy
  • 18,498
  • 2
  • 46
  • 92
3

I'm not sure I really understand how the example is to be generalised, but this removes all List heads at level 2 and 4:

ReplacePart[lis, Position[lis, List, {#}] & /@ Join[2, 4] -> Sequence]

(* {a, {b, c, d, e}, {f, g, h, i}} *)
Simon Woods
  • 84,945
  • 8
  • 175
  • 324