Adding elements to an array in Perl - arrays

I have this code where I want to add 10, 11 and 12 to array arr.
my #num=(0,1,2);
my $i=10;
for my $d (#num){
if (defined($d)) {
my #arr;
$arr[$d] = $i;
$i=$i+1;
my $dvv=dump(\#arr);
print "**** $dvv \n";
}
}
The output is:
**** [10]
**** [undef, 11]
**** [undef, undef, 12]
Why is only the last element of array defined?

AntonH's answer addresses the specific problem with your specific code, but there are actually ways to rewrite your code that would avoid the problem entirely. A more "Perlish" way to accomplish the same thing would be:
my #arr;
for my $i (0 .. 2) {
push(#arr, $i + 10);
}
Or:
my #arr = map { $_ + 10 } 0 .. 2;
Or just:
my #arr = 10 .. 12;

Since you have the declaration of the array within the loop, it will re-create it each time, removing any values that would have been placed in it on previous iterations of the loop.
You should declaure #arr before the loop if you want the values to stay:
my #arr;
for my $d (#num) {
...
}
And because of this line:
$arr[$d];
$d is the position defined by the other array (0, then 1, then 2). So it puts the value of $i in that position in the array, and puts values before to undef.

Related

Printing "Multi-Dimensional" Array in Perl

I am having a problem attempting to print an array that contains arrays. When printing the array #dev which contains the other arrays, I am only managing to print the first three as it is indicated by the #printing in-line comments. The commented line #print($dev[4][2]); works fine, as well as any of the other combination of numbers within the allowable range. For some reason the for loop does not work. Help!?
my #dev;
my #tf;
my #a;
my #t;
my #f;
my #ofv;
my #tfv;
#tf = ('123456787', '123456788', '123456789'); #printing
#a = (78, 65, 57); #printing
#t = (70, 55, 42); #printing
#f = (77, 64, 56);
#ofv = ('true', 'false', 'false');
#tfv = ('false', 'true', 'true');
#dev = (
[#tf],
[#a],
[#t],
[#f],
[#ofv],
[#tfv],
);
#print($dev[4][2]);
for (my $i = 0; $i <= (scalar(#tf) - 1); $i++) {
for (my $j = 0; $j <= (scalar(#dev) - 1); $j++) {
print($dev[$i][$j]);
print("\n");
}
}
Thank you.
If you just want to show the data of such complex data struct, the modules Data::Dumper or Smart::Comments may be good options.
use Data::Dumper;
print Dumper(\#dev);
or
use Smart::Comments;
### #dev
The output is much more perl-style and not that readable, but is quite convenient to show the struct of such complex data.
Perl can be quite compact.
This snippet of code do the same thing for my arrays #arr1, #arr2 and #arr3:
#arr1 = (1..10);
#arr2 = ('a'..'j');
#arr3 = ('.') x 10;
#multid = \(#arr1, #arr2, #arr3);
print "#$_\n" for (#multid);
OUTPUT:
1 2 3 4 5 6 7 8 9 10
a b c d e f g h i j
. . . . . . . . . .
Also the [] copies an array and gives a reference to it (It's an anonymous array in memory, regardless of the array, a copy of which he is). If there is no need to such duplicate, it is better to use the backslash \ which instead gives a reference to existing array without coping. (like & operator in C, as tell us perldoc)
Your outermost for loop is constrained by the length of t, which is 3. It will never print more than three arrays.
If I understand what you're trying to do, you need top swap #t and #dev. That will print all your values.
That won't, however, print any array that is longer than 3 (the length of dev).
For that, you need:
#dev = (
[#tf], # Probably meant tf
[#a],
[#t],
[#f],
[#ofv],
[#tfv],
);
#print($dev[4][2]);
for (my $i = 0; $i < #dev; $i++) {
for (my $j = 0; $j < #{ $dev[$i] }; $j++) {
print($dev[$i][$j]);
print("\n");
}
}

Is it possible to assign two variables in Perl foreach loop?

Is it possible to assign two variables the same data from an array in a Perl foreach loop?
I am using Perl 5, I think I came across something in Perl 6.
Something like this:
my $var1;
my $var2;
foreach $var1,$var2 (#array){...}
It's not in the Perl 5 core language, but List::Util has a pairs function which should be close enough (and a number of other pair... functions which may be more convenient, depending on what you're doing inside the loop):
#!/usr/bin/env perl
use strict;
use warnings;
use 5.010;
use List::Util 'pairs';
my #list = qw(a 1 b 2 c 3);
for my $pair (pairs #list) {
my ($first, $second) = #$pair;
say "$first => $second";
}
Output:
a => 1
b => 2
c => 3
The easiest way to use this is with a while loop that calls splice on the first two elements of the array each time,
while (my($var1, $var2) = splice(#array, 0, 2)) {
...
}
However, unlike foreach, this continually does a double-shift on the original array, so when you’re done, the array is empty. Also, the variables assigned are copies, not aliases as with foreach.
If you don’t like that, you can use a C-style for loop:
for (my $i = 0; $i < #array; $i += 2) {
my($var1, $var2) = #array[$i, $i+1];
...
}
That leaves the array in place but does not allow you to update it the way foreach does. To do that, you need to address the array directly.
my #pairlist = (
fee => 1,
fie => 2,
foe => 3,
fum => 4,
);
for (my $i = 0; $i < #pairlist; $i += 2) {
$pairlist[ $i + 0 ] x= 2;
$pairlist[ $i + 1 ] *= 2;
}
print "Array is #pairlist\n";
That prints out:
Array is feefee 2 fiefie 4 foefoe 6 fumfum 8
You can get those into aliased variables if you try hard enough, but it’s probably not worth it:
my #kvlist = (
fee => 1,
fie => 2,
foe => 3,
fum => 4,
);
for (my $i = 0; $i < #kvlist; $i += 2) {
our ($key, $value);
local(*key, $value) = \#kvlist[ $i, $i + 1 ];
$key x= 2;
$value *= 2;
}
print "Array is #kvlist\n";
Which prints out the expected changed array:
Array is feefee 2 fiefie 4 foefoe 6 fumfum 8
Note that the pairs offered by the List::Pairwise module, which were but very recently added to the core List::Util module (and so you probably cannot use it), are still not giving you aliases:
use List::Util 1.29 qw(pairs);
my #pairlist = (
fee => 1,
fie => 2,
foe => 3,
fum => 4,
);
for my $pref (pairs(#pairlist)) {
$pref->[0] x= 2;
$pref->[1] *= 2;
}
print "Array is #pairlist\n";
That prints out only:
Array is fee 1 fie 2 foe 3 fum 4
So it didn’t change the array at all. Oops. :(
Of course, if this were a real hash, you could double the values trivially:
for my $value (values %hash) { $value *= 2 }
The reasons that works is because those are aliases into the actual hash values.
You cannot change the keys, since they’re immutable. However, you can make a new hash that’s an updated copy of the old one easily enough:
my %old_hash = (
fee => 1,
fie => 2,
foe => 3,
fum => 4,
);
my %new_hash;
#new_hash{ map { $_ x 2 } keys %old_hash } =
map { $_ * 2 } values %old_hash;
print "Old hash is: ", join(" " => %old_hash), "\n";
print "New hash is: ", join(" " => %new_hash), "\n";
That outputs
Old hash is: foe 3 fee 1 fum 4 fie 2
New hash is: foefoe 6 fiefie 4 fumfum 8 feefee 2
A general algorithm for more than 2 variables:
while( #array ){
my $var1 = shift #array;
my $var2 = shift #array;
my $var3 = shift #array;
# other variables from #array
# do things with $var1, $var2, $var3, ...
}
PS: Using a working copy of the array to that it is preserved for use later:
if( my #working_copy = #array ){
while( #working_copy ){
my $var1 = shift #working_copy;
my $var2 = shift #working_copy;
my $var3 = shift #working_copy;
# other variables from #working_copy
# do things with $var1, $var2, $var3, ...
}
}
PPS: another way is to use indexing. Of course, that is a sure sign that the data structure is wrong. It should be an array of arrays (AoA) or an array of hashes (AoH). See perldoc perldsc and perldoc perllol.
my $i = 0;
while( $i < #array ){
my $var1 = $array[ $i++ ];
my $var2 = $array[ $i++ ];
my $var3 = $array[ $i++ ];
# other variables from #array
# do things with $var1, $var2, $var3, ...
}
PPPS: I've been asked to clarify why the data structure is wrong. It is a flatten set of tuples (aka records aka datasets). The tuples are recreated by counting of the number of data for each. But what is the reader constructing the set has a bug and doesn't always get the number right? If, for a missing value, it just skips adding anything? Then all the remaining tuples are shifted by one, causing the following tuples to be grouped incorrectly and therefore, invalid. That is why an AoA is better; only the tuple with the missing data would be invalid.
But an better structure would be an AoH. Each datum would access by a key. Then new or optional data can be added without breaking the code downstream.
While I'm at it, I'll add some code examples:
# example code for AoA
for my $tuple ( #aoa ){
my $var1 = $tuple->[0];
my $var2 = $tuple->[1];
my $var3 = $tuple->[2];
# etc
}
# example code for AoH
for my $tuple ( #aoh ){
my $var1 = $tuple->{keyname1};
my $var2 = $tuple->{key_name_2};
my $var3 = $tuple->{'key name with spaces'};
my $var4 = $tuple->{$key_name_in_scalar_variable};
# etc
}
Here is a module-less way to "loop" by an arbitrary value ($by) and output the resulting group of elements using an array slice:
#!perl -l
#array = "1".."6";
$by = 3; $by--;
for (my $i = 0 ; $i < #array ; $i += $by ) {
print "#array[$i..$i+$by]";
$i++ ;
}
As a one-liner to test (cut and paste to a Unix shell):
perl -E '#array = "1".."6"; $by = 3; $by--;
for (my $i = 0 ; $i < #array ; $i += $by ) {
say "#array[$i..$i+$by]"; $i++ }'
Output:
1 2 3
4 5 6
If you make $by = 2; it will print pairs of numbers. To get at specific elements of the resulting slice access it as an anonymous array: (e.g. [#array[$i..$i+$by]]->[1]).
See also:
How do I read two items at a time in a Perl foreach loop?
Perl way of iterating over 2 arrays in parallel
Some good responses there, including reference to natatime which is quite easy to use. It's easy to implement too - it is essentially a wrapper around the splice solutions mentioned in the responses here.
The following is not the nicest example, but I've been using autobox::Core and made an #array->natatime() "method" ;-) like this:
use autobox::Core ;
sub autobox::Core::ARRAY::natatime {
my ($self, $by) = #_;
my #copy = #$self ;
my #array ;
push #array, [splice (#copy, 0, $by) ] while #copy ;
if ( not defined wantarray ) {
print "#{ $_ } \n" for #array ;
}
return wantarray ? #array : \#array;
}
The #copy array is spliced destructively, but $self (which is how the #array in front of the autobox method -> arrow gets passed to the function) is still there. So I can do:
my #dozen = "1" .. "12" ; # cakes to eat
#dozen->natatime(4) ; # eat 4 at time
my $arr_ref = #dozen->natatime(4) ; # make a reference
say "Group 3: #{ $arr_ref->[2] }" ; # prints a group of elements
say scalar #dozen , " cakes left" ; # eat cake; still have it
Output:
1 2 3 4
5 6 7 8
9 10 11 12
Group 3: 9 10 11 12
12 cakes left
One other approach that also uses a CPAN module (I gave this answer elsewhere but it is worth repeating). This can also be done non-destructively, with Eric Strom's excellent List::Gen module:
perl -MList::Gen=":all" -E '#n = "1".."6"; say "#$_" for every 2 => #n'
1 2
3 4
5 6
Each group of elements you grab is returned in an anonymous array so the individual values are in: $_->[0] $_->[1] ... etc.
You mentioned Perl6, which handles multiple looping values nicely:
my #qarr = 1 .. 6;
my ($x, $y, $z) ;
for #qarr -> $x , $y , $z { say $x/$y ; say "z = " ~ $z }
Output:
0.5
z = 3
0.8
z = 6
For more on the Perl6 approach see: Looping for Fun and Profit from the 2009 Perl6 Advent Calendar, or the Blocks and Statements Synopsis for details. Perhaps Perl 5 will have a similar "loop by multliple values" construct one day - à la perl5i's foreach :-)

Loop over one dimension of a multi-dimensional array in Perl using for each

* UPDATED* for typos
Another PERL question.... I am trying to loop through a 2D array. I am positive about the size of one dimension but unsure on the second. The code snippet:
foreach my $value (#surfaces[1])
{
my $sum = 0;
my $smallest = 9999;
my $limit_surface = 0;
for (my $i = 0; $i < 3; $i++)
{
$sum += $surfaces[$i][$counter];
if ($surfaces[$i][$counter] <= $smallest)
{
$smallest = $surfaces[$i][$counter];
$limit_surface = $subchannel_number[$i];
}
}
$counter++;
push(#avg_value,$sum/#rodsurface_number);
push(#limiting_schan,$limit_surface);
push(#limiting_value,$smallest);
}
I am compiled but $value variable is failing to initialize.
Repeat after me:
Perl does not have multidimensional arrays
Perl does not have multidimensional arrays
Perl does not have multidimensional arrays
What Perl does have is have are arrays that contain references pointing to other arrays. You can emulate multidimensional arrays in Perl, but they are not true multidimensional arrays. For example:
my #array;
$array[0] = [ 1, 2, 3, 4, 5 ];
$array[1] = [ 1, 2, 3 ];
$array[2] = [ 1, 2 ];
I can talk about $array[0][1], and $array[2][1], but while $array[0][3] exists, $array[2][3] doesn't exist.
If you don't understand references, read the tutorial on references.
What you need to do is go through your array and then find out the size of each subarray and go through each of those. There's no guarantee that
The reference contained in your primary array actually points to another array:
That your sub-array contains only scalar data.
You can use the $# operator to find the size of your array. For example $#array is the number of items in your array. You an use ( 0..$#array ) to go through each item of your array, and this way, you have the index to play around with.
use strict;
use warnings;
my #array;
$array[0] = [ 1, 2, 3, 4, 5 ];
$array[1] = [ 1, 2, 3 ];
$array[2] = [ 1, 2, ];
#
# Here's my loop for the primary array.
#
for my $row ( 0..$#array ) {
printf "Row %3d: ", $row ;
#
# My assumption is that this is another array that contains nothing
# but scalar data...
#
my #columns = #{ $array[$row] }; # Dereferencing my array reference
for my $column ( #columns ) {
printf "%3d ", $column;
}
print "\n";
}
Note I did my #columns = #{ $array[$row] }; to convert my reference back into an array. This is an extra step. I could have simply done the dereferencing in my for loop and saved a step.
This prints out:
Row 0: 1 2 3 4 5
Row 1: 1 2 3
Row 2: 1 2
I could put some safety checks in here. For example, I might want to verify the size of each row, and if one row doesn't match the other, complain:
my $row_size = $array[0];
for my $row ( 1..$#array ) {
my #columns = #{ $array[$row] };
if ( $#columns ne $array_size ) {
die qq(This is not a 2D array. Not all rows are equal);
}
}
You do not describe your data structure, nor explain exactly what you want to do with it. This limits the advice that we can give to just the general variety.
If you're trying to iterate over an array of arrays, I would advise you to do it based off of element instead of index.
For example, below I have a 4 by 5 matrix of integers. I would like to find the average of these values. One way to do this is to simply iterate over each row and then column, and add up the values:
use strict;
use warnings;
my #AoA = (
[11, 12, 13, 14, 15],
[21, 22, 23, 24, 25],
[31, 32, 33, 34, 35],
[41, 42, 43, 44, 45],
);
my $sum = 0;
my $count = 0;
for my $row (#AoA) {
for my $element (#$row) { # <-- dereference the array ref
$sum += $element;
$count++;
}
}
print "Average of Matrix is " . ($sum / $count) . "\n";
Outputs:
Average of Matrix is 28
For more information on complex data structures, check out: Perl Data Structures Cookbook
I've set up some dummy variables and changed a few things around. This compiles and produces the results I show below.
This might not answer your question, but should allow you to copy and paste the code, run it yourself, edit the input and see how the output compares to what you want.
use warnings;
use strict;
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
my #surfaces = ( ['1','2','3'],
['10','20','30'],
['100','200','400'],
);
my #subchannel_number = ( ['1','2','3'],
['10','20','30'],
['100','200','400'],
);
my #rodsurface_number = (1 .. 10);
my $counter = 0;
my (#avg_value, #limiting_schan, #limiting_value);
foreach my $value ($surfaces[1]){
my $sum = 0;
my $smallest = 9999;
my $limit_surface = 0;
for (my $i = 0; $i < 3; $i++) {
$sum += $surfaces[$i][$counter];
if ($surfaces[$i][$counter] <= $smallest){
$smallest = $surfaces[$i][$counter];
$limit_surface = $subchannel_number[$i];
}
}
$counter++;
push(#avg_value,$sum/#rodsurface_number);
push(#limiting_schan,$limit_surface);
push(#limiting_value,$smallest);
}
print Dumper (\#avg_value, \#limiting_schan, \#limiting_value);
$VAR1 = [
'11.1'
];
$VAR2 = [
[
'1',
'2',
'3'
]
];
$VAR3 = [
1
];

searching two array string for equal words

I am a beginner in Perl. I have two string arrays array1 and array2. I want to check the each and every element in 2nd array. if there is i want to give a relative value one to that particular element in the 2nd array. the relative values are store in an array.I try it out but it wont work and git gives a warning like" Use of uninitialized value in string eq at pjt.pl line 52, line 3".
while($i <= (scalar #resultarray-1))
{
while ($j <= (scalar #inputsymbl-1))
{
if ($resultarray[$i] eq $inputsymbl[$j])
{
$rel[$j]=1;
$i=$i+1;
$j=0;
}
else
{
$j=$j+1;
}
}
if($j==(scalar #inputsymbl))
{
$i=$i+1;
$j=0;
}
}
try this:
my $i = 0;
my $j = 0;
## walk each array element
foreach(#resultarray) {
my $result = $_;
foreach(#inputsymbl) {
my $symbl = $_;
if ($result eq $symbl) {
$rel[$j] = 1;
$i++;
} else {
$j++;
}
}
if ($j == (scalar #inputsymbl - 1)) {
$i++;
$j = 0;
}
}
provide more informations if you need detailed help.
From your question and code, it appears that you want to flag the indexes, by using a third array, of the two array's elements that are equal. By doing this, however, you're creating a sparse array. Also, if the two arrays don't have the same number of elements, a "Use of uninitialized value in string eq..." warning will eventually occur. Given these issues, consider using the smaller index of the two arrays (done using the ternary operator below) and pushing the indexes of the equal elements onto the third array:
use strict;
use warnings;
use Data::Dumper;
my #results;
my #arr1 = qw/A B C D E F G H I J/;
my #arr2 = qw/A D C H E K L H N J P Q R S T/;
# Equal: ^ ^ ^ ^ ^
# Index: 0 2 4 7 9
for my $i ( 0 .. ( $#arr1 <= $#arr2 ? $#arr1 : $#arr2 ) ) {
push #results, $i if $arr1[$i] eq $arr2[$i];
}
print Dumper \#results;
Output:
$VAR1 = [
0,
2,
4,
7,
9
];
Hope this helps!

deleting elements in array in a loop Perl

I want to make a loop where I go through all the elements in one array (#array1), and if the same element is found in another array (#array2), I want the value from a third array (#array3) with the same index to be added to the first array and deleted from the third array. I tried it this way, however the line with the if-statement runs on unitialized values, and it loops forever.
foreach my $elem1 (#array1){
my $i = 0;
while ($i < scalar #array2){
if($array2[$i]==$elem1){
push (#array1, $array3[$i]);
delete $array2[$i];
}
else{
$i++;
}
}
}
The problem is you do not increment $i if the element matches. Delete the else.
Well, here's one way to deal with the problem (to the extent that I understand what you want to do). Whenever you need to answer questions about membership, you probably want to use a hash.
use strict;
use warnings;
my #array1 = ( 11, 22, 33, 44);
my #array2 = ( 11, 2, 3, 44, 5, 44);
my #array3 = (-11, -2, -3, -44, -5, -444);
# Keep track of every value in #array1.
my %vals = map { $_ => 1 } #array1;
my #keep_indexes;
# Go through #array2, either adding the corresponding
# value in #array3 to #array1 or storing the array index.
for my $i (0 .. $#array2) {
if ($vals{$array2[$i]}){
push #array1, $array3[$i];
}
else {
push #keep_indexes, $i;
}
}
# Rebuild #array3 from the indexes that we stored.
# Change this to #array2 if you want to delete from that array instead.
#array3 = map $array3[$_], #keep_indexes;
print join(' ', #array1), "\n"; # 11 22 33 44 -11 -44 -444
print join(' ', #array2), "\n"; # 11 2 3 44 5 44
print join(' ', #array3), "\n"; # -2 -3 -5
I dislike that code, so here are a few caveats:
Any time you have numbered variable names (#array1, #array2, etc.), you're headed for confusion. You need better variable names or, more likely, a better data structure.
Any time you find yourself in the business of maintaining parallel arrays, you should consider whether a better data structure would help.
You are deleting an element from array2, not array3 as stated in the question. I think the delete operation sets the array element to undef. The next time around the loop it checks that same element that is now undef against $elem. Hence the error. Then it does he same again and again.

Resources