There's more than one way to do things PerlMonks

### n-dim array generator

by jo37 (Hermit)
 on Jun 16, 2020 at 19:54 UTC Need Help??

Every now and then I need to create a n-dimensional array from some given formula, where n almost always is 1 or 2.
I've been searching for a module providing such functionality without success. It would be a surprise if there really wasn't such a thing.

But so I rolled my own and came to this:

```# arg_1 to arg_n specify size in dimension k,
# last arg is a reference to a sub
# - expecting n arguments x1 ... xn
# - returning the desired value at \$na->[x1]...[xn]
sub narray {
my \$size = shift;
my \$val = pop;
my \$na;
for my \$i (0 .. \$size - 1) {
\$na->[\$i] = @_ ? narray(@_, sub {&\$val(\$i, @_)}) : &\$val(\$i);
}
\$na;
}
which can be used in various ways and is not limited in dimension:
```\$ar = narray(3, sub {\$_[0]});
# [0, 1, 2]

\$mr = narray(3, 3, sub {\$_[0] == \$_[1] || 0});
# [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
just to get an idea.

Does anybody know of a module providing something similar?
Otherwise: Is this of use for someone else or would it even fit into something existing?

UPDATE 1: This would be more perlish if the sub ref argument came first and the sub itself was prototyped:

```sub narray (&@);

\$ar = narray {\$_[0]} 3;

UPDATE 2: Incorporating LanX's suggestion of using map into UPDATE 1 results in:

```sub narray (&@);
sub narray (&@) {
my \$val = shift;
my \$size = shift;

[map {my \$i = \$_; @_ ? narray {&\$val(\$i, @_)} @_ : &\$val(\$i)} (0 .
+. \$size - 1)];
}

my \$na = narray {"<@_>"} 3, 2
# [['<0 0>', '<0 1>'], ['<1 0>', '<1 1>'], ['<2 0>', '<2 1>']]

Greetings,
-jo

\$gryYup\$d0ylprbpriprrYpkJl2xyl~rzg??P~5lp2hyl0p\$

Replies are listed 'Best First'.
Re: n-dim array generator
by LanX (Sage) on Jun 17, 2020 at 13:01 UTC
TIMTOWTDI

I'm also showing the classic hard-coded approach with nested loops and auto-vivification for comparison.

```use strict;
use warnings;
use Data::Dump qw/pp dd/;
use Test::More;

sub matrix (&@){
my \$code = shift;

my \$rec;
my @coor;
my \$idx;

\$rec = sub  {
my \$level = shift;
return \$code->(@coor) unless @_;

my \$max = shift;
my \$arr = [
map {
\$coor[\$level] = \$_;
\$rec->(\$level+1,@_)
} 0 .. \$max
];

return \$arr;
};

\$rec->(0,@_);
}

my \$a_matrix = matrix {"<@_>"} 3,2,1;

my @classic;
for my \$x (0..3) {
for my \$y (0..2) {
for my \$z (0..1) {
\$classic[\$x][\$y][\$z] = "<\$x \$y \$z>";
}
}
}

#pp \@classic;

is_deeply(\$a_matrix,\@classic,"same matrix");

pp \$a_matrix;

done_testing;

```ok 1 - same matrix
[
[
["<0 0 0>", "<0 0 1>"],
["<0 1 0>", "<0 1 1>"],
["<0 2 0>", "<0 2 1>"],
],
[
["<1 0 0>", "<1 0 1>"],
["<1 1 0>", "<1 1 1>"],
["<1 2 0>", "<1 2 1>"],
],
[
["<2 0 0>", "<2 0 1>"],
["<2 1 0>", "<2 1 1>"],
["<2 2 0>", "<2 2 1>"],
],
[
["<3 0 0>", "<3 0 1>"],
["<3 1 0>", "<3 1 1>"],
["<3 2 0>", "<3 2 1>"],
],
]
1..1

Cheers Rolf
(addicted to the Perl Programming Language :)
Wikisyntax for the Monastery

The recursion can be simplified, since \$level depends on the length of the remaining dimensions in @_.

I also used a "fancy" trick with \$cursor as ref to the @coor array.

(EDIT: Of course there is still more room for improvement, it depends if clarity or speed matters)

```use strict;
use warnings;
use Data::Dump qw/pp dd/;
use Test::More;

sub matrix2 (&@){
my \$code = shift;

my @coor;
my \$depth = @_;

my \$rec;
\$rec = sub  {

return \$code->(@coor) unless @_;

my \$level  = \$depth - @_;
my \$max    = shift;
my \$cursor = \\$coor[\$level];

my \$arr = [
map {
\$\$cursor = \$_;
\$rec->(@_)
} 0 .. \$max
];

return \$arr;
};

\$rec->(@_);
}

my \$a_matrix = matrix2 {"<@_>"} 3,2,1;

my @classic;
for my \$x (0..3) {
for my \$y (0..2) {
for my \$z (0..1) {
\$classic[\$x][\$y][\$z] = "<\$x \$y \$z>";
}
}
}

is_deeply(\$a_matrix,\@classic,"same matrix");

pp \@classic;

done_testing;

```ok 1 - same matrix
[
[
["<0 0 0>", "<0 0 1>"],
["<0 1 0>", "<0 1 1>"],
["<0 2 0>", "<0 2 1>"],
],
[
["<1 0 0>", "<1 0 1>"],
["<1 1 0>", "<1 1 1>"],
["<1 2 0>", "<1 2 1>"],
],
[
["<2 0 0>", "<2 0 1>"],
["<2 1 0>", "<2 1 1>"],
["<2 2 0>", "<2 2 1>"],
],
[
["<3 0 0>", "<3 0 1>"],
["<3 1 0>", "<3 1 1>"],
["<3 2 0>", "<3 2 1>"],
],
]
1..1

Cheers Rolf
(addicted to the Perl Programming Language :)
Wikisyntax for the Monastery

Sure, there are several ways to implement it. That's not what I'm actually interested in - from the usage point of view it doesn't matter too much. Such a piece of code is just not enough that would justify it's own module. If only any of these were available as an export!

Greetings,
-jo

\$gryYup\$d0ylprbpriprrYpkJl2xyl~rzg??P~5lp2hyl0p\$
and here a variation using a for-loop instead of a map, probably easier to grasp:

```use strict;
use warnings;
use Data::Dump qw/pp dd/;
use Test::More;

sub matrix3 (&@){
my (\$code,@dims) = @_;

my @coor =(undef)x@dims;

my \$rec;
\$rec = sub  {
my @dims =@_;

return \$code->(@coor) unless @dims;

my \$level  = - @dims;
my \$max    = shift @dims;
my \$cursor = \\$coor[\$level];

my @arr;

for ( 0 .. \$max ) {
\$\$cursor = \$_;
\$arr[\$_] = \$rec->(@dims);
}

return \@arr;
};

return \$rec->(@dims);
}

my \$a_matrix = matrix3 {"<@_>"} 3,2,1;

my @classic;
for my \$x (0..3) {
for my \$y (0..2) {
for my \$z (0..1) {
\$classic[\$x][\$y][\$z] = "<\$x \$y \$z>";
}
}
}

is_deeply(\$a_matrix,\@classic,"same matrix");

pp \@classic;

done_testing;

Cheers Rolf
(addicted to the Perl Programming Language :)
Wikisyntax for the Monastery

Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://11118149]
Approved by marto
Front-paged by LanX
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others contemplating the Monastery: (1)
As of 2021-09-18 19:18 GMT
Sections?
Information?
Find Nodes?
Leftovers?
Voting Booth?

No recent polls found

Notices?