The top answer in this post: How can I create a multidimensional array in Perl? suggests building a multi-dimensional array as follows:
my #array = ();
foreach my $i ( 0 .. 10 ) {
foreach my $j ( 0 .. 10 ) {
push #{ $array[$i] }, $j;
}
}
I am wondering if there is a way of building the array more compactly and avoiding the nested loop, e.g. using something like:
my #array = ();
my #other_array = (0 ... 10);
foreach my $i ( 0 .. 10 ) {
$array[$i] = #other_array; # This does not work in Perl
}
}
Does Perl support any syntax like that for building multi-dimensional arrays without nested looping?
Similarly, is there a way to print the multidimensional array without (nested) looping?
There is more than one way to do it:
Generating
push accepts LISTs
my #array;
push #{$array[$_]}, 0 .. 10 for 0 .. 10;
Alternative syntax:
my #array;
push #array, [ 0 .. 10 ] for 0 .. 10;
map eye-candy
my #array = map { [ 0 .. 10 ] } 0 .. 10;
Alternative syntax:
my #array = map [ 0 .. 10 ], 0 .. 10;
Printing
With minimal looping
print "#$_\n" for #array;
On Perl 5.10+
use feature 'say';
say "#$_" for #array;
With more formatting control
print join( ', ', #$_ ), "\n" for #array; # "0, 1, 2, ... 9, 10"
"No loops" (The loop is hidden from you)
use Data::Dump 'dd';
dd #array;
Data::Dumper
use Data::Dumper;
print Dumper \#array;
Have a look at perldoc perllol for more details
You are close, you need a reference to the other array
my #array; # don't need the empty list
my #other_array = (0 ... 10);
foreach my $i ( 0 .. 10 ) {
$array[$i] = \#other_array;
# or without a connection to the original
$array[$i] = [ #other_array ];
# or for a slice
$array[$i] = [ #other_array[1..$#other_array] ];
}
}
You can also make anonymous (unnamed) array reference directly using square braces [] around a list.
my #array;
foreach my $i ( 0 .. 10 ) {
$array[$i] = [0..10];
}
}
Edit: printing is probably easiest using the postfix for
print "#$_\n" for #array;
for numerical multidimensional arrays, you can use PDL. It has several constructors for different use cases. The one analogous to the above would be xvals. Note that PDL objects overload printing, so you can just print them out.
use PDL;
my $pdl = xvals(11, 11);
print $pdl;
Related
I have a situation where input is in form of
$n followed by n lines having elements of n different arrays.
like
2
1 2 3
1 6
means i have 2 arrays with elements as 1,2,3 and 1,6.
Now i really dont know how big N can be. How to create dynamic arrays and store value to them. Arrays may be named array1,array2 or any other method to differentiate different arrays.
$n = <STDIN>;
for ($i = 0; $i < $n; $i++) {
$l = <STDIN>;
#arr = split(" ", $l);
}
Please improve this code.
You can use an array of arrays:
use strict;
my #array;
while(<STDIN>) {
my #line = split(" ", $_);
push #array, \#line;
}
# Just to display what's inside your array:
use Data::Dumper;
print Dumper(\#array);
Or even shorter:
use strict;
my #array;
push #array, [split ' ', $_] while(<STDIN>);
This question already has answers here:
How can I use a variable as a variable name in Perl?
(3 answers)
Closed 8 years ago.
I have N array and i want to print 1st elements of them in single for loop
my code:
#arr1=qw(1..5);
#arr2=qw(6..10);
...
#arrN=qw(...);
for($i=1;$i<=$N;$i++)
{
print "$arr[$i][0]";
}
When you find that you need to know the names of 0 .. N different variables. Its time to consider that you might be doing it wrong.
Arrays = list of 0 .. N values, can be sequential
Hash = list of 0 .. N named values
For your arrays, unless you actually want to be converting to strings, don't use qw() just use the bare ()
See solution below, you need an array of arrays:
#!/usr/bin/perl
use strict;
use warnings;
my $n = 10;
my #nArrays;
#fills the nArrays list with array_refs
for my $num(0 .. $n){
push #nArrays, [($num .. $num+5)];
}
#Print out the arrays, one per row, long way
for my $array (#nArrays){
print $array->[0], "\n";
}
If, contrary to all normal recommendations, you leave use strict; out of your script, you could use:
$N = 3;
#arr1 = (1..5);
#arr2 = (6..10);
#arr3 = (7..12);
#arrs = ("arr1", "arr2", "arr3");
for ($i = 0; $i < $N; $i++)
{
print "$arrs[$i][0]\n";
}
Output:
1
6
7
This is absolutely not recommended, but it does work still (mainly for reasons of backwards compatibility).
With use strict; etc, you can use explicit references:
use strict;
use warnings;
my $N = 3;
my #arr1 = (1..5);
my #arr2 = (6..10);
my #arr3 = (7..12);
my #arrs = (\#arr1, \#arr2, \#arr3);
for (my $i = 0; $i < $N; $i++)
{
print "$arrs[$i][0]\n";
}
Output:
1
6
7
On the whole, though, you would do better with a single array of arrays, perhaps like this:
use strict;
use warnings;
my $N = 3;
my #arr = ( [1..5], [6..10], [7..12] );
for (my $i = 0; $i < $N; $i++)
{
print "$arr[$i][0]\n";
}
Output:
1
6
7
What you have in mind is called a symbolic reference, and generally not a good idea.
If the values of these variables belong together, there is already a data structure that is indexed by an integer: Use an array to hold them:
use strict;
use warnings;
my #arr = (
[ 1 .. 5 ],
[ 6 .. 10 ],
# ...
[ 1_000_000 .. 1_000_005 ],
);
for my $i (0 .. $#arr) {
print $arr[$i][0], "\n";
}
I am reading an ordered file for which I must count by-hour, by-minute or by-second occurrences. If requested, I must print times with 0 occurrences (normalized output) or skip them (non-normalized output). The output must obviously be ordered.
I first thought using an array. When the output is non normalized, I am doing roughly the equivalent of:
#array[10] = 100;
#array[10000] = 10000;
And to print the result:
foreach (#array) {
print if defined;
}
Is there a way to reduce iterations to only elements defined in the array? In the previous example, that would mean doing only two iterations, instead of 10000 as using $#array implies. Then I would also need a way to know the current array index in a loop. Does such a thing exist?
I am thinking more and more to use a hash instead. Using a hash solves my problem and also eliminates the need to convert hh:mm:ss times to index and vice-versa.
Or do you have a better solution to suggest for this simple problem?
Yes, use a hash. You can iterate over the ordered array of the keys of the hash if your keys sort correctly.
You can also remember just the pairs of numbers in an array:
#!/usr/bin/perl
use warnings;
use strict;
my #ar = ( [ 10, 100 ],
[ 100, 99 ],
[ 12, 1 ],
[ 13, 2 ],
[ 15, 1 ],
);
sub normalized {
my #ar = sort { $a->[0] <=> $b->[0] } #_;
map "#$_", #ar;
}
sub non_normalized {
my #ar = sort { $a->[0] <=> $b->[0] } #_;
unshift #ar, [0, 0] unless $ar[0][0] == 0;
my #return;
for my $i (0 .. $#ar) {
push #return, "#{ $ar[$i] }";
push #return, $_ . $" . 0 for 1 + $ar[$i][0] .. $ar[$i + 1][0] - 1;
}
return #return;
}
print join "\n", normalized(#ar), q();
print "\n";
print join "\n", non_normalized(#ar), q();
I have a CSV file that I use split to parse into an array of N items, where N is a multiple of 3.
Is there a way i can do this
foreach my ( $a, $b, $c ) ( #d ) {}
similar to Python?
I addressed this issue in my module List::Gen on CPAN.
use List::Gen qw/by/;
for my $items (by 3 => #list) {
# do something with #$items which will contain 3 element slices of #list
# unlike natatime or other common solutions, the elements in #$items are
# aliased to #list, just like in a normal foreach loop
}
You could also import the mapn function, which is used by List::Gen to implement by:
use List::Gen qw/mapn/;
mapn {
# do something with the slices in #_
} 3 => #list;
You can use List::MoreUtils::natatime. From the docs:
my #x = ('a' .. 'g');
my $it = natatime 3, #x;
while (my #vals = $it->()) {
print "#vals\n";
}
natatime is implemented in XS so you should prefer it for efficiency. Just for illustration purposes, here is how one might implement a three element iterator generator in Perl:
#!/usr/bin/perl
use strict; use warnings;
my #v = ('a' .. 'z' );
my $it = make_3it(\#v);
while ( my #tuple = $it->() ) {
print "#tuple\n";
}
sub make_3it {
my ($arr) = #_;
{
my $lower = 0;
return sub {
return unless $lower < #$arr;
my $upper = $lower + 2;
#$arr > $upper or $upper = $#$arr;
my #ret = #$arr[$lower .. $upper];
$lower = $upper + 1;
return #ret;
}
}
}
my #list = (qw(one two three four five six seven eight nine));
while (my ($m, $n, $o) = splice (#list,0,3)) {
print "$m $n $o\n";
}
this outputs:
one two three
four five six
seven eight nine
#z=(1,2,3,4,5,6,7,8,9,0);
for( #tuple=splice(#z,0,3); #tuple; #tuple=splice(#z,0,3) )
{
print "$tuple[0] $tuple[1] $tuple[2]\n";
}
produces:
1 2 3
4 5 6
7 8 9
0
Not easily. You'd be better off making #d an array of three-element tuples, by pushing the elements onto the array as an array reference:
foreach my $line (<>)
push #d, [ split /,/, $line ];
(Except that you really ought to use one of the CSV modules from CPAN.
As of Perl v5.36 you can do exactly that:
foreach my ( $a, $b, $c ) ( #d ) { ... }
It's implemented as for_list experimental feature, so you can ignore the warning the usual way with use experimental qw(for_list);
For versions before v5.36 we'll rely on while/splice as mentioned above.
I have a fixed-sized array where the size of the array is always in factor of 3.
my #array = ('foo', 'bar', 'qux', 'foo1', 'bar', 'qux2', 3, 4, 5);
How can I cluster the member of array such that we can get
an array of array group by 3:
$VAR = [ ['foo','bar','qux'],
['foo1','bar','qux2'],
[3, 4, 5] ];
my #VAR;
push #VAR, [ splice #array, 0, 3 ] while #array;
or you could use natatime from List::MoreUtils
use List::MoreUtils qw(natatime);
my #VAR;
{
my $iter = natatime 3, #array;
while( my #tmp = $iter->() ){
push #VAR, \#tmp;
}
}
I really like List::MoreUtils and use it frequently. However, I have never liked the natatime function. It doesn't produce output that can be used with a for loop or map or grep.
I like to chain map/grep/apply operations in my code. Once you understand how these functions work, they can be very expressive and very powerful.
But it is easy to make a function to work like natatime that returns a list of array refs.
sub group_by ($#) {
my $n = shift;
my #array = #_;
croak "group_by count argument must be a non-zero positive integer"
unless $n > 0 and int($n) == $n;
my #groups;
push #groups, [ splice #array, 0, $n ] while #array;
return #groups;
}
Now you can do things like this:
my #grouped = map [ reverse #$_ ],
group_by 3, #array;
** Update re Chris Lutz's suggestions **
Chris, I can see merit in your suggested addition of a code ref to the interface. That way a map-like behavior is built in.
# equivalent to my map/group_by above
group_by { [ reverse #_ ] } 3, #array;
This is nice and concise. But to keep the nice {} code ref semantics, we have put the count argument 3 in a hard to see spot.
I think I like things better as I wrote it originally.
A chained map isn't that much more verbose than what we get with the extended API.
With the original approach a grep or other similar function can be used without having to reimplement it.
For example, if the code ref is added to the API, then you have to do:
my #result = group_by { $_[0] =~ /foo/ ? [#_] : () } 3, #array;
to get the equivalent of:
my #result = grep $_->[0] =~ /foo/,
group_by 3, #array;
Since I suggested this for the sake of easy chaining, I like the original better.
Of course, it would be easy to allow either form:
sub _copy_to_ref { [ #_ ] }
sub group_by ($#) {
my $code = \&_copy_to_ref;
my $n = shift;
if( reftype $n eq 'CODE' ) {
$code = $n;
$n = shift;
}
my #array = #_;
croak "group_by count argument must be a non-zero positive integer"
unless $n > 0 and int($n) == $n;
my #groups;
push #groups, $code->(splice #array, 0, $n) while #array;
return #groups;
}
Now either form should work (untested). I'm not sure whether I like the original API, or this one with the built in map capabilities better.
Thoughts anyone?
** Updated again **
Chris is correct to point out that the optional code ref version would force users to do:
group_by sub { foo }, 3, #array;
Which is not so nice, and violates expectations. Since there is no way to have a flexible prototype (that I know of), that puts the kibosh on the extended API, and I'd stick with the original.
On a side note, I started with an anonymous sub in the alternate API, but I changed it to a named sub because I was subtly bothered by how the code looked. No real good reason, just an intuitive reaction. I don't know if it matters either way.
Or this:
my $VAR;
while( my #list = splice( #array, 0, 3 ) ) {
push #$VAR, \#list;
}
Another answer (a variation on Tore's, using splice but avoiding the while loop in favor of more Perl-y map)
my $result = [ map { [splice(#array, 0, 3)] } (1 .. (scalar(#array) + 2) % 3) ];
Try this:
$VAR = [map $_ % 3 == 0 ? ([ $array[$_], $array[$_ + 1], $array[$_ + 2] ])
: (),
0..$#array];
Another generic solution, non-destructive to the original array:
use Data::Dumper;
sub partition {
my ($arr, $N) = #_;
my #res;
my $i = 0;
while ($i + $N-1 <= $#$arr) {
push #res, [#$arr[$i .. $i+$N-1]];
$i += $N;
}
if ($i <= $#$arr) {
push #res, [#$arr[$i .. $#$arr]];
}
return \#res;
}
print Dumper partition(
['foo', 'bar', 'qux', 'foo1', 'bar', 'qux2', 3, 4, 5],
3
);
The output:
$VAR1 = [
[
'foo',
'bar',
'qux'
],
[
'foo1',
'bar',
'qux2'
],
[
3,
4,
5
]
];
As a learning experience I decided to do this in Perl6
The first, perhaps most simplest way I tried was to use map.
my #output := #array.map: -> $a, $b?, $c? { [ $a, $b // Nil, $c // Nil ] };
.say for #output;
foo bar qux
foo1 bar qux2
3 4 5
That didn't seem very scalable. What if I wanted to take the items from the list 10 at a time, that would get very annoying to write. ... Hmmm I did just mention "take" and there is a keyword named take lets try that in a subroutine to make it more generally useful.
sub at-a-time ( Iterable \sequence, Int $n where $_ > 0 = 1 ){
my $is-lazy = sequence.is-lazy;
my \iterator = sequence.iterator;
# gather is used with take
gather loop {
my Mu #current;
my \result = iterator.push-exactly(#current,$n);
# put it into the sequence, and yield
take #current.List;
last if result =:= IterationEnd;
}.lazy-if($is-lazy)
}
For kicks let's try it against an infinite list of the fibonacci sequence
my $fib = (1, 1, *+* ... *);
my #output = at-a-time( $fib, 3 );
.say for #output[^5]; # just print out the first 5
(1 1 2)
(3 5 8)
(13 21 34)
(55 89 144)
(233 377 610)
Notice that I used $fib instead of #fib. It was to prevent Perl6 from caching the elements of the Fibonacci sequence.
It might be a good idea to put it into a subroutine to create a new sequence everytime you need one, so that the values can get garbage collected when you are done with them.
I also used .is-lazy and .lazy-if to mark the output sequence lazy if the input sequence is. Since it was going into an array #output it would have tried to generate all of the elements from an infinite list before continuing onto the next line.
Wait a minute, I just remembered .rotor.
my #output = $fib.rotor(3);
.say for #output[^5]; # just print out the first 5
(1 1 2)
(3 5 8)
(13 21 34)
(55 89 144)
(233 377 610)
.rotor is actually far more powerful than I've demonstrated.
If you want it to return a partial match at the end you will need to add a :partial to the arguments of .rotor.
Use the spart function from the List::NSect package on CPAN.
perl -e '
use List::NSect qw{spart};
use Data::Dumper qw{Dumper};
my #array = ("foo", "bar", "qux", "foo1", "bar", "qux2", 3, 4, 5);
my $var = spart(3, #array);
print Dumper $var;
'
$VAR1 = [
[
'foo',
'bar',
'qux'
],
[
'foo1',
'bar',
'qux2'
],
[
3,
4,
5
]
];
Below a more generic solution to the problem:
my #array = ('foo', 'bar', 1, 2);
my $n = 3;
my #VAR = map { [] } 1..$n;
my #idx = sort map { $_ % $n } 0..$#array;
for my $i ( 0..$#array ){
push #VAR[ $idx[ $i ] ], #array[ $i ];
}
This also works when the number of items in the array is not a factor of 3.
In the above example, the other solutions with e.g. splice would produce two arrays of length 2 and one of length 0.