9

What's the shortest way to flat a multidimensional array?
Here's some examples of what I mean:

# 2D array
my @a = [1,2],[3,4];
say @a».Slip.flat;                # prints (1 2 3 4)
# 3D array
my @b = [[1,2],[3,4]],[[5,6],[7,8]];
say @b».Slip».flat».Slip.flat;    # prints (1 2 3 4 5 6 7 8)
                                  # but needs to know how many dimensions
                                  # there are to flatten

Is it possible to recursively flatten an array of arrays such as @b without writing a sub which recursively descends into it or having any knowledge of the number of its dimensions?
I'm asking this because I trust the compiler (now or in a future implementation) to be able to optimize more an operator-based solution than a sub.

Fernando Santagata
  • 1,487
  • 1
  • 8
  • 15
  • 2
    One day you'll be able to use `[**]`. For now, you could use `[*;*;*;*;*]` (you can specify more dimensions than actually exist). See https://stackoverflow.com/a/37230217/1077672 Do you think your question is a duplicate of one of the linked questions or warrants its own answer? – raiph Oct 13 '18 at 09:02
  • @raiph that's documented, but it's not indexed: https://docs.perl6.org/language/subscripts#Multiple_dimensions – jjmerelo Oct 13 '18 at 09:25

2 Answers2

8

Not sure if there's a more compact way, but

say do gather @b.deepmap(*.take);

should do it.

If you do not care for the order of the flattened result,

say do gather @b>>.take;

might also be an option, though it 'feels' wrong to me to (ab)use hyper operators for side effects...


Until proper handling of HyperWhatever slicing suggested by raiph arrives in core, you could add your own sugar @b[**] covering this specific use case via

multi sub postcircumfix:<[ ]>(\SELF, HyperWhatever:D $, *% where !*) {
    gather SELF.deepmap(*.take);
}
Christoph
  • 164,997
  • 36
  • 182
  • 240
  • 1
    Would `sub postfix:<[**]> ($_) { gather .deepmap: *.take }` make sense? When `**` gets implemented (per my comment on Fernando's question) then, assuming it's faster, one can just remove this sub. – raiph Oct 13 '18 at 09:46
  • maybe - depends on your view of 'jagged' arrays (in contrast to shaped ones); I guess `.[**]` flattening the whole thing could make sense; the proper way to implement this would be like https://github.com/rakudo/rakudo/blob/master/src/core/array_slice.pm6#L448 – Christoph Oct 13 '18 at 10:35
  • 1
    having thought about this a bit, given that we're talking about perl (DWIM!) and that `.[2][3]` and `.[2;3]` both work interchangeably on jagged arrays, change that 'maybe' to 'yes' – Christoph Oct 13 '18 at 10:57
  • 2
    :) .oO ( [Coming to `6.f`](http://design.perl6.org/S09.html#Mixing_subscripts): `@calendar{ July; **[0..2; *-3..*-1] } # Last three business hours of first three days in July`. ) – raiph Oct 13 '18 at 11:07
0
say @a>>.List.flat

should do the trick, however i have noticed that a Range will break the recursion:

my @a = [[1,2],[3,4],5,[6,7],[8,9,0],[[1,[2,3,4,[ 23,56,^7]],5,6,]],7,8,9,0];
say @a>>.List.flat;   #prints (1 2 3 4 5 6 7 8 9 0 1 2 3 4 [23 56 ^7] 5 6 7 8 9 0)

i don't know if this is a bug or not.

  • 1
    it's not the range that does it, but the level of depth - see eg `say [[[[1,],],],]>>.List.flat` – Christoph Oct 13 '18 at 17:03
  • indeed you're right. can you explain where this nesting level limit comes from ? it's not obvious to me. – HelpfulGuy Oct 13 '18 at 18:04
  • I didn't look at the code to verify, but what seems to happen is that `>>.List` will listify only the first sub-level of the array (ie it does not descend recursively) by virtue of the operation you passed to the hyper as well as the top level for a reason I'm not quite clear on (I would have expected the top-level to remain an Array); these first two levels will be transparent to the call to `.flat`, which will then flatten out another level of depth; however, that's where it stops if that last level was an array, as its elements are held in scalar containers, which are items – Christoph Oct 13 '18 at 18:45