Yaml input perl eternal looping issue - perl

$description is Input from a yaml file of format
main_key:
-
key1:value2
key2:value2
-
key1:value1
key2:value2
Basically that is a hash of array of hashes.
I input $description and process the inner hash as follows:
while ( my ( $mod, $defined ) = each %{ $description } ) {
my $index = 0;
foreach ( #{ $defined } ) {
while ( my ( $key, $value ) = each %{ $_ } ) {
process ( $key, $mod, $description, $index );
}
$index = $index + 1;
}
}
When certain 'keyword' is used as a key I replace add more key,value pairs to the inner hash
function1() and function2() return a hash pointer.
sub process {
my ( $key, $mod, $description, $index ) = #_;
my $parameters;
if ( $key eq 'keyword' ) {
$parameters = function1( );
}
else {
$parameters = function2( );
}
$description->{$mod}[$index] = { %$parameters, %{$description->{$mod}[$index]} };
}
The issue here is that "while ( my ( $key, $value ) = each %{ $_ } )" in the main code runs forever, using the same key and value over and over again.

Yeah. Don't do that.
Never modify a hash while looping over it. From perldoc -f each:
If you add or delete a hash's elements while iterating over it,
entries may be skipped or duplicated--so don't do that.
The general pattern is to build up the list of modifications, and then make them after the end of the loop. You can, of course, embed that sequence in an outer loop that iterates over the hash until there are no more modifications that need to be made.

This refactoring of your code works fine. I have rewritten process to do all that is necessary for the innermost hashes. I have named these $item as I don't know what they are supposed to represent. Please amend this to something more descriptive.
There never was any reason to pass all those parameters, as the values of $description, $mod, and $index were only used to locate the hash in question using $description->{$mod}[$index] so it may as well have been passed directly as a reference, which is what I do. In addition, because process now loops over the array contents there is no need to pass $key either, so the subroutine now has just one parameter.
Each element of $item is examined, and the new hash of data to be added for that element is obtained from function1 or function2 as appropriate and pushed onto #params instead of being inserted straight away.
Once all the new values have been established, they are all added into $item and the process is complete.
for my $defined (values %$description) {
process($_) for #$defined;
}
sub process {
my ($item) = #_;
my #params;
for my $key (keys %$item) {
push #params, $key eq 'keyword' ? function1() : function2();
}
for my $params (#params) {
#{$item}{keys %$params} = values %{$params};
}
}

Related

powershell double for loop

Need advice on loop
$Variable contains 11111 22222
foreach ($variable in $value) {
for ([byte]$c = [char]'b'; $c -le [char]'c'; $c++) {
$variable."([char]$c)" } }
I am looking output as 11111b and then 22222c but currently, I am getting 11111b , 11111c and then 22222b and then 22222c.
Kindly advice
I am assuming you mean that $value, not $variable, contains 11111 and 22222, specifically in an array.
Since you want $c to maintain its value between iterations of the foreach loop you need to initialize $c outside of the foreach loop. Therefore, you really don't need (or, rather, should not use) two loops at all.
$value = 11111, 22222;
[Byte] $c = [Char] 'b';
foreach ($variable in $value)
{
"$variable$([Char] $c++)"
}
This gives the output you are seeking:
11111b
22222c

Perl unexpected result

Imagine I have this Perl script
my $name = " foo ";
my $sn = " foosu";
trim($name, \$sn);
print "name: [$name]\n";
print "sn: [$sn]\n";
exit 0;
sub trim{
my $fref_trim = sub{
my ($ref_input) = #_;
${$ref_input} =~ s/^\s+// ;
${$ref_input} =~ s/\s+$// ;
};
foreach my $input (#_){
if (ref($input) eq "SCALAR"){
$fref_trim->($input);
} else {
$fref_trim->(\$input);
}
}
}
Result:
name: [foo]
sn: [foosu]
I would expect $name to be "[ foo ]" when printing the value after calling trim, but the sub is setting $name as I would want it. Why is this working, when it really shouldn't?
I'm not passing $name by reference and the trim sub is not returning anything. I'd expect the trim sub to create a copy of the $name value, process the copy, but then the original $name would still have the leading and trailing white spaces when printed in the main code.
I assume it is because of the alias with #_, but shouldn't the foreach my $input (#_) force the sub to copy the value and only treat the value not the alias?
I know I can simplify this sub and I used it only as an example.
Elements of #_ are aliases to the original variables. What you are observing is the difference between:
sub ltrim {
$_[0] =~ s/^\s+//;
return $_[0];
}
and
sub ltrim {
my ($s) = #_;
$s =~ s/^\s+//;
return $s;
}
Compare your code to:
#!/usr/bin/env perl
my $name = " foo ";
my $sn = " foosu";
trim($name, \$sn);
print "name: [$name]\n";
print "sn: [$sn]\n";
sub trim {
my #args = #_;
my $fref_trim = sub{
my ($ref_input) = #_;
${$ref_input} =~ s/^\s+//;
${$ref_input} =~ s/\s+\z//;
};
for my $input (#args) {
if (ref($input) eq "SCALAR") {
$fref_trim->($input);
}
else {
$fref_trim->(\$input);
}
}
}
Output:
$ ./zz.pl
name: [ foo ]
sn: [foosu]
Note also that the loop variable in for my $input ( #array ) does not create a new copy for each element of the array. See perldoc perlsyn:
The foreach loop iterates over a normal list value and sets the scalar variable VAR to be each element of the list in turn. ...
...
the foreach loop index variable is an implicit alias for each item in the list that you're looping over.
In your case, this would mean that, at each iteration $input is an alias to the corresponding element of #_ which itself is an alias to the variable that was passed in as an argument to the subroutine.
Making a copy of #_ thus prevents the variables in the calling context from being modified. Of course, you could do something like:
sub trim {
my $fref_trim = sub{
my ($ref_input) = #_;
${$ref_input} =~ s/^\s+//;
${$ref_input} =~ s/\s+\z//;
};
for my $input (#_) {
my $input_copy = $input;
if (ref($input_copy) eq "SCALAR") {
$fref_trim->($input_copy);
}
else {
$fref_trim->(\$input_copy);
}
}
}
but I find making a wholesale copy of #_ once to be clearer and more efficient assuming you do not want to be selective.
I assume it is because of the alias with #_, but shouldn't the foreach my $input (#_) force the sub to copy the value and only treat the value not the alias?
You're right that #_ contains aliases. The part that's missing is that foreach also aliases the loop variable to the current list element. Quoting perldoc perlsyn:
If any element of LIST is an lvalue, you can modify it by modifying VAR inside the loop. Conversely, if any element of LIST is NOT an lvalue, any attempt to modify that element will fail. In other words, the foreach loop index variable is an implicit alias for each item in the list that you're looping over.
So ultimately $input is an alias for $_[0], which is an alias for $name, which is why you see the changes appearing in $name.

Perl: How to access an array thats inside of 3 hashes passed to subroutine by reference

I have a code that is something like this:
foreach $item (#total_data)
{
setinfo($item);
} # #total_data contains an array of references to hashes (\%hash1 ... \%hashN)
In the subrutine goes something like this:
sub setinfo
{
my ($argument) = #_;
my $i = 0;
#inside original hash $argument{"data"}{"fulldraw"} there is an [array]
#that contains numbers of the form XYYZ and I want to split them into
#the following pairs XY YY YZ but that code works ok#
foreach $item (${$argument{"data"}{"fulldraw"}})
{
my $match;
my $matchedstr;
if ($item =~ /^\d{4}$/)
{
...
}
else
{
print STDERR "DISCARDED: $item\n";
}
}
}
I know I am probably making the mistake in how I am dereferencing it, but couldn't figure it out with all the articles I've read on the internet.
Thanks!
#{ ... } # dereference
Maybe $argument is a hashref; you need to use
foreach $item (#{ $argument->{data}->{fulldraw} })
Just use dereference #{ ... }:
foreach $item (#{ $argument->{data}{fulldraw} })
You can use Data::Dumper to visualize complex structures:
use Data::Dumper;
print Dumper($argument);

Issue while comparing hashes of array in perl

I am pretty new to Perl and need to accomplish a task quickly. Any help is appreciated!
I have two hash of arrays as follows:
Hash 1
-------
abc.txt: ['0744','0']
xyz.txt: ['0744','0']
Hash 2
-------
abc.txt: ['0766','0']
x.txt: ['0744','0']
I have to compare these 2 hashes print 3 things:
1. Files Added in Hash2
2. Files Missing in Hash2
3. Files(keys) which are present in both hashes but there attributes(values) are different.
print "-------------------------ADDED FILES--------------------------------";
foreach (keys %hash2){
print "added $_\n" unless exists $hash1{$_};
}
print "-------------------------MISSING FILES--------------------------------";
foreach (keys %hash1){
print "Missing $_\n" unless exists $hash2{$_};
}
print "-------------------------Different permissions--------------------------------";
foreach my $key2 ( keys %hash2 ) {
unless ( exists $hash1{$key2} ) { next; };
if (join(",", sort #{ $hash1{$_}})
eq join(",", sort #{ $hash2{$_}}) ){
}
else{
print "value is different";
}
}
Issue is when keys are same.This for each loop doesn't work well.I want to print like this:
FileName: File Attributes Before : File Attributes after
abc.txt: '0744','0': 0766','0'
Please help
Your code didn't work, because you defined my $key2 in your foreach-loop, which leaves $_ as an empty value.
Also you don't need to join the hashes. Try the smartmatch operator on array values, its more efficient since you only need to do the join, when you want to have an output.
foreach my $key2 ( keys %hash2 ) {
unless ( exists $hash1{$key2} ) { next; };
unless ( $hash1{$key2} ~~ $hash2{ $key2 } )
{
print "$key2: ".join(",", #{ $hash1{$key2}}).": ".join(",", #{ $hash2{$key2}})."\n"
}
}
Change
foreach my $key2 ( keys %hash2 ) {
unless ( exists $hash1{$key2} ) { next; };
if (join(",", sort #{ $hash1{$_}})
eq join(",", sort #{ $hash2{$_}}) ){
}
else{
print "value is different";
}
}
to
foreach my $key2 ( keys %hash2 ) {
next unless ( exists $hash1{$key2} );
my $val1 = join(",", sort #{ $hash1{$key2} });
my $val2 = join(",", sort #{ $hash2{$key2} });
if ($val1 eq $val2) {
# values are same
}
else {
print "$key2 $val1 $val2\n";
}
}
and try again.

the analysis of my $file = ${$chainro->{$ro}->{$id}}[$i];

I have a two level hash %chainro , each key of $chainro{$ro}{$id}points to an array. The following code is to iterate through the first level of hash, $chainro->{$ro}. I can guess what
does my $file = ${$chainro->{$ro}->$id}}[$i]; aim to perform. However, I do not know why ${$chainro->{$ro}->{$id}}was written this way? In specific, why do we need to add ${ } to wrap the $chainro->${ro}->{$id}
foreach my $id (keys %{$chainro->{$ro}})
{
$size = $#{$chainro->{$ro}->{$id}};
for ($i=0; $i<$size; $i++)
{
my $file = ${$chainro->{$ro}->{$id}}[$i];
}
}
${ EXPR1 }[ EXPR2 ]
is an alternate way of writing
EXPR1->[ EXPR2 ]
so
${ $chainro->{$ro}->{$id} }[$i]
can be written as
$chainro->{$ro}->{$id}->[$i]
or even as
$chainro->{$ro}{$id}[$i]
Cleaned up:
for my $id (keys %{ $chainro->{$ro} }) {
my $files = $chainro->{$ro}{$id};
for my $i (0..$#$files) {
my $file = $files->[$i];
...
}
}
Or if you don't need $i:
for my $id (keys %{ $chainro->{$ro} }) {
my $files = $chainro->{$ro}{$id};
for my $file (#$files) {
...
}
}
It is to dereference a reference to something.
The something is an array here.