What's wrong with this statement in Perl? - perl

print "$_", join(',',sort keys %$h),"\n";
It's giving me an error below:
Use of uninitialized value in string at missing_months.pl line 36.
1,10,11,12
this print statement is present in a for loop as below:
foreach my $num ( sort keys %hash )
{
my $h = $hash{$num};
print "$_", join(',',sort keys %$h),"\n";
}

No need for the "$_". That line should be:
print join (',' , sort {$a <=> $b} keys %$h),"\n";
While the $_ is treated as the default iterator in for and foreach loops (see perlvar), you've already assigned the iterator variable as $num.
Here is how to use the $_ correctly in a single line:
print join(',', sort { $a <=> $b } keys %{$hash{$_}}),"\n" foreach keys %hash;
On a Side Note...
sort uses string comparison by default, meaning that '10' is deemed to come before '2'. It seems that you're dealing with months (perhaps?), which is why I've used the numerical comparison block { $a <=> $b }.

Related

What is the difference between #{$value} and #$value in perl?

Example:
for my $key ( keys %DNS_CSV_FILES){
for my $file (#$DNS_CSV_FILES{$key}){
print $file;
}
}
gives the error:
Global symbol "$DNS_CSV_FILES" requires explicit package name (did you forget to declare "my $DNS_CSV_FILES"?) at dns.pl line 41.
Execution of dns.pl aborted due to compilation errors.
But this code:
for my $key ( keys %DNS_CSV_FILES){
for my $file (#{$DNS_CSV_FILES{$key}}){
print $file;
}
}
gives the desired output:
file1.txtfile2.txt
#$x{ $key } is short for #{ $x }{ $key }, not #{ $x{ $key } }.
See Perl Dereferencing Syntax. Footnote [1] in particular. The curlies can only be omitted around a simple scalar variable.
There is no difference between #{ $x } and #$x. But that's not what the two snippets are using.
The first is using #$x{ $key }, which is short for #{ $x }{ $key }.
There is a difference between #{ $x }{ $key } and #{ $x{ $key } }.
#foo{ $key } is a slice of a named array, so #{ ... }{ $key } is a slice of a referenced array. #{ $DNS_CSV_FILES }{ $key } is therefore a slice of the array referenced by scalar $DNS_CSV_FILES.
#foo is a array provided by name, so #{ ... } is a referenced array. #{ $DNS_CSV_FILES{ $key } } is therefore an array referenced by hash element $DNS_CSV_FILES{ $key }.
In short, Perl's dereference syntax puts braces around the reference. However, you can leave off the braces if the reference is simple scalar, like $value. For anything else, including a hash key lookup, you keep the braces.
That's the old-style "circumfix" notation. Perl v5.24 stabilized the postfix dereference syntax.
This
#$DNS_CSV_FILES{$key}
Will from the left side see an array sigil # followed by a scalar $. This can only be the dereferencing of an array ref. Otherwise the # is a syntax error. Despite you putting the hash notation at the end. It is a race condition, of sorts. So it will assume that what follows is a scalar, and not a hash value.
When you clarify by adding extra brackets, it becomes clear what is intended
#{ $DNS_CSV_FILES{$key} }
Whatever is inside #{ } must be an array ref, and $....{key} must be a hash value.

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.

Sort Perl hash from largest to smallest

I am looking at an example found here: http://perlmeme.org/tutorials/sort_function.html
And it gives this code to sort a hash based on each key's value:
# Using <=> instead of cmp because of the numbers
foreach my $fruit (sort {$data{$a} <=> $data{$b}} keys %data) {
print $fruit . ": " . $data{$fruit} . "\n";
}
This code I do not fully understand, but when I experiment with it, it sorts from lowest to highest. How can I flip it to sort from highest to lowest?
Just use reverse sort instead of sort.
foreach my $fruit (reverse sort keys %data) { ...
Swap $a and $b:
foreach my $fruit (sort {$data{$b} <=> $data{$a}} keys %data) {

Can I pass arguments to the compare subroutine of sort in Perl?

I'm using sort with a customized comparison subroutine I've written:
sub special_compare {
# calc something using $a and $b
# return value
}
my #sorted = sort special_compare #list;
I know it's best use $a and $b which are automatically set, but sometimes I'd like my special_compare to get more arguments, i.e.:
sub special_compare {
my ($a, $b, #more) = #_; # or maybe 'my #more = #_;' ?
# calc something using $a, $b and #more
# return value
}
How can I do that?
Use the sort BLOCK LIST syntax, see perldoc -f sort.
If you have written the above special_compare sub, you can do, for instance:
my #sorted = sort { special_compare($a, $b, #more) } #list;
You can use closure in place of the sort subroutine:
my #more;
my $sub = sub {
# calc something using $a, $b and #more
};
my #sorted = sort $sub #list;
If you want to pass the elements to be compared in #_, set subroutine's prototype to ($$). Note: this is slower than unprototyped subroutine.

How can I sort a hash's keys naturally?

I have a Perl hash whose keys start with, or are, numbers.
If I use,
foreach my $key (sort keys %hash) {
print $hash{$key} . "\n";
}
the list might come out as,
0
0001
1000
203
23
Instead of
0
0001
23
203
1000
foreach my $key (sort { $a <=> $b} keys %hash) {
print $hash{$key} . "\n";
}
The sort operation takes an optional comparison "subroutine" (either as a block of code, as I've done here, or the name of a subroutine). I've supplied an in-line comparison that treats the keys as numbers using the built-in numeric comparison operator '<=>'.
Paul's answer is correct for numbers, but if you want to take it a step further and sort mixed words and numbers like a human would, neither cmp nor <=> will do. For example,
9x
14
foo
fooa
foolio
Foolio
foo12
foo12a
Foo12a
foo12z
foo13a
Sort::Naturally takes care of this problem, providing the nsort and ncmp routines.
Your first problem is the body of the loop (which no other answer here seems to point out).
foreach my $key ( sort keys %hash ) {
print $hash{$key} . "\n";
}
We don't know what the keys of %hash are. We just know that they that are handed to you as $key, in lexical order, inside the loop. You then use the keys to access the contents of the hash, printing each entry.
The values of the hash do not come out in a sorted order, because you sort on the keys.
Would you instead want to output the values in sorted order, consider the following loop:
foreach my $value ( sort values(%hash) ) {
printf( "%s\n", $value );
}
This loop does print the values in the order you observe:
0
0001
1000
203
23
To sort them numerically instead, use
foreach my $value ( sort { $a <=> $b } values(%hash) ) {
printf( "%s\n", $value );
}
This produces
0
0001
23
203
1000
which is what you wanted.
See the Perl manual for the sort function for further information and many more examples.
$key (sort { $a <=> $b} keys %hash)
will do the trick
Or descending sort:
$key (sort { $b <=> $a} keys %hash)
Or even
$key (sort { $a <=> $b} values %hash)
$key (sort { $b <=> $a} values %hash)