I have a perl hash that I am indexing like this:
my %hash;
$hash{'number'}{'even'} = [24, 44, 38, 36];
$hash{'number'}{'odd'} = [23, 43, 37, 35];
When I try to print key names like this:
foreach my $key (keys %hash{'number'})
{
print "Key: $key\n";
}
I get the following error:
Type of arg 1 to keys must be hash (not hash slice) at test.pl
However when I pass the array ref to a function and print it there, it prints the values:
test(\%hash);
sub test
{
my ($hash) = #_;
foreach my $key (keys %{$hash->{'number'}})
{
print "Key: $key\n"; #outputs: even odd
}
}
Can someone please let me know what is going wrong here? Also if I have multi-keyed hash which I have in this case where hash is indexed by both 'number' and 'even' or 'odd' if I do something like this:
foreach my $key (keys %hash)
{
print "First Key: $key\n"; #Outputs number
}
Then will I always get 'number' as the output right and I can never get 'even', 'odd' as outputs, correct? This is just to know good coding practice :)
This is the full code:
sub test
{
my ($hash) = #_;
foreach my $key (keys %{$hash->{'number'}})
{
print "Key: $key\n";
}
}
my %hash;
$hash{'number'}{'even'} = [24, 44, 38, 36];
$hash{'number'}{'odd'} = [23, 43, 37, 35];
test(\%hash);
foreach my $key (keys %hash)
{
print "First Key: $key\n";
}
foreach my $key (keys %hash{'number'})
{
print "Key: $key\n";
}
Thanks,
Newbie
my %hash;
$hash{'number'}{'even'} = [24, 44, 38, 36];
$hash{'number'}{'odd'} = [23, 43, 37, 35];
%hash is a hash whose keys are strings ('number'), and whose values are hash references.
foreach my $key (keys %hash{'number'})
{
print "Key: $key\n";
}
To refer to a value that's part of %hash, you want to write $hash{'number'}, not %hash{'number'}.
But $hash{'number'} is a hash reference, not a hash. To refer to the hash that it refers to, you can write this:
%{$hash{'number'}}
Putting it all together this:
my %hash;
$hash{'number'}{'even'} = [24, 44, 38, 36];
$hash{'number'}{'odd'} = [23, 43, 37, 35];
foreach my $key (keys %{$hash{'number'}}) {
print "Key: $key\n";
}
will produce this output:
Key: even
Key: odd
(possibly not in that order).
You can do something like this:
#!/usr/bin/perl -w
use strict;
use warnings;
my %hash;
$hash{'number'}{'even'} = [24, 44, 38, 36];
$hash{'number'}{'odd'} = [23, 43, 37, 35];
foreach my $i(keys %hash){
print $i;
foreach my $j(keys %{$hash{$i}}){
print "\t".$j."\t";
print join(" ",#{$hash{'number'}{$j}})."\n";
}
}
Related
I have a hash with a certain set of data. I need to manipulate the hash values so that I can get result like below:
Expected Output:
key_1=Cell1
Val_1=C3#C4#C1#C2
Script:
#!/usr/bin/perl
use strict; use warnings;
use Data::Dumper;
use List::Util qw /uniq/;
my %hash = (
'Cell1' => {
'A' => [ 'C1','C2','C1','C2' ],
'B' => [ 'C3','C3','C4','C4' ]
}
);
print Dumper(\%hash);
my $i = 0;
foreach my $key (keys %hash) {
++$i;
print "key_$i=$key\n";
foreach my $refs (keys %{ $hash{$key} }) {
print "Val_$i=", join('#', uniq #{$hash{$key}{$refs}})."\n";
}
}
Current Output:
key_1=Cell1
Val_1=C3#C4
Val_1=C1#C2
How can I get the expected output here?
You can use an additional array (#cells) to store the values before you print:
foreach my $key (keys %hash) {
++$i;
print "key_$i=$key\n";
my #cells;
foreach my $refs (keys %{ $hash{$key} }) {
push #cells, #{$hash{$key}{$refs}};
}
print "Val_$i=", join('#', uniq #cells)."\n";
}
Prints:
key_1=Cell1
Val_1=C3#C4#C1#C2
The order is not guaranteed since you retrieve the keys from a hash. You could use sort to make the order predicatable.
The shown code uses values for each key one at a time (for A, then for B ...) . Instead, assemble all values using map on the list of all keys
my $i = 0;
for my $key (keys %hash) {
++$i;
say "key_$i=$key";
say "Val_$i=",
join "#", uniq map { #{ $hash{$key}->{$_} } } keys %{$hash{$key}};
}
I'm creating keys outside and inside a subroutine on the same hash. However, after the subroutine, the values in the keys I created before the subroutine is called, are now interpreted as array references.
#!/usr/bin/perl
use module;
use strict;
use warnings;
my %hash;
my $count = 0;
my #array = ("a", "b", "c", "d");
for my $letter (#array) {
$hash{$letter} = $count;
$count++;
}
# need "\" to pass in hash otherwise changes
# will get lost outside of subroutine
foreach my $x (sort keys %hash) {
print "first $hash{$x}\n";
}
module::add_ten(\%hash);
foreach my $p (sort keys %hash) {
# $hash{$p} is printing array references, but before it was
# printing the value I desired. What did the subroutine do?
print "second $hash{$p} $hash{$p}->{ten}\n";
}
and here is the module with the subroutine
package module;
sub add_ten {
my $count = 10;
# this passes the full array as reference
my ($hash_ref) = #_; # $hash_ref is actually %hash (yes, the % is not a typo)
my #keys = keys $hash_ref;
foreach my $ltr (sort keys $hash_ref) {
$hash_ref->{$ltr} = { ten => $count };
$count++;
}
}
1;
here is the output:
first 0
first 1
first 2
first 3
second HASH(0x7ff0c3049c50) 10
second HASH(0x7ff0c3049bc0) 11
second HASH(0x7ff0c3049b90) 12
second HASH(0x7ff0c3049b60) 13
I'm expecting the output to be:
first 0
first 1
first 2
first 3
second 0 10
second 1 11
second 2 12
second 3 13
I modified my module:
package module;
sub add_ten {
my $count = 10;
# this passes the full array as reference
my ($hash_ref) = #_; # $hash_ref is actually %hash (yes, the % is not a typo)
my #keys = keys $hash_ref;
foreach my $ltr (sort keys $hash_ref) {
$hash_ref->{$ltr}{ten}=$count;
$count++;
}
}
1;
and the main script (needed to comment out use strict to get it to work):
#!/usr/bin/perl
use module;
#use strict;
use warnings;
my %hash;
my $count = 0;
my #array = ("a", "b", "c", "d");
for my $letter (#array) {
$hash{$letter} = $count;
$count++;
}
# need "\" to pass in hash otherwise changes
# will get lost outside of subroutine
foreach my $x (sort keys %hash) {
print "first $hash{$x}\n";
}
module::add_ten(\%hash);
foreach my $p (sort keys %hash) {
print "second $hash{$p} $hash{$p}{ten}\n";
}
But this is what I was trying to get to.
$hash_ref is a reference to %hash, so when you change the values of the elements of the hash referenced by $hash_ref, you're changing the values of the hash %hash.
That means that when you do
$hash_ref->{$ltr} = { ten => $count };
You are doing
$hash{a} = { ten => 10 };
It should be no surprise that $hash{a} no longer contains zero. You'll have to change your data structure. You could use the following:
$hash{a}{value} = 0;
$hash{a}{subhash}{ten} = 10;
I have a hash of hash value in a YAML::Tiny object, as
$yaml = YAML::Tiny->read_string($config);
Now, I am trying to check for a specific value inside this hash of hash, and if the value matches, take it's key, by
foreach my $key (keys %{$yaml->[0]}){
if ( values %{$yaml->[0]->{$key}} =~ /My_String/){
print $key;
}
But it is giving
Applying pattern match (m//) to %hash will act on scalar(%hash) at script.pl line 25.
Type of argument to keys on reference must be unblessed hashref or arrayref at script.pl line 25.
I guess it is because values %{$yaml->[0]->{$key} is also returning a hash, because the object itself is a hash of hash. What am I doing wrong here?
Test data::
'string_1' => {
'test_data' => 82,
'test_data1' => 99,
'test_data2' => My_string
},
'string_2' => {
'test_data3' => 97,
'test_data4' => 67
}
};
and I am looking for printing test_data2 because it's value is My_string
There are two problems.
Precedence: Perl understands the expression as values( %{...} =~ /.../ ):
$ perl -MO=Deparse,-p -e 'values %{ {a=>3} } =~ /x/'
values((%{+{'a', 3};} =~ /x/));
Even after fixing it: Binding operator can be used on a scalar (string). values returns a list. What exactly are you trying to do?
Update: The following scripts does what you want while still trying to keep the logic you had in mind, i.e. it replaces the inner for-loop with a grep.
#!/usr/bin/perl
use warnings;
use strict;
my $yaml = [{
'string_1' => {
'test_data' => 82,
'test_data1' => 99,
'test_data2' => 'My_String'
},
'string_2' => {
'test_data3' => 97,
'test_data4' => 67
}
}];
for my $key (keys %{$yaml->[0]}){
if (my #matches = grep $yaml->[0]{$key}{$_} =~ /My_String/,
keys %{ $yaml->[0]->{$key} }
){
print "$key: #matches\n";
}
}
If your structure is a hash of hashes, then you need to explicitly traverse your data:
use strict;
use warnings;
use YAML;
my $hashref = Load(<<'END_YAML');
---
string_1:
test_data: 82
test_data1: 99
test_data2: My_string
string_2:
test_data3: 97
test_data4: 67
END_YAML
SEARCH:
while (my ($k1, $v1) = each %$hashref) {
while (my ($k2, $v2) = each %$v1) {
if ($v2 =~ /My_string/) {
print "$k1 -> $k2 -> $v2\n";
last SEARCH;
}
}
}
Outputs:
string_1 -> test_data2 -> My_string
Anyone have any idea how to sort number by length?
Ex : (11,111,122,12,2,13,21,15,211,22,213,2004)
I wanted the sorted array to be:
11
12
13
15
111
122
2
21
22
213
2004
The desired output seems to indicate you don't just want to sort by the number of digits, but rather first sort by the first digit and then by the length.
The desired output you show omits 211, so I just put it where it belonged according to my understanding.
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
my #source = (11, 111, 122, 12, 2, 13, 21, 15, 211, 22, 213, 2004);
my #desired = (11, 12, 13, 15, 111, 122, 2, 21, 22, 211, 213, 2004);
my #sorted =sort {
substr($a, 0, 1) <=> substr($b, 0, 1) ||
length($a) <=> length($b) ||
$a <=> $b # thanks #ikegami
} #source;
is_deeply(\#sorted, \#desired, 'Sorted and desired are the same');
my #sorted =
sort { substr($a,0,1) <=> substr($b,0,1) || $a <=> $b }
#unsorted;
gives the order you requested. Or maybe you want
my #sorted =
sort { substr($a,0,1) <=> substr($b,0,1)
|| length($a) <=> length($b)
|| $a <=> $b }
#unsorted;
If 211 wasn't missing from the output you provided, I could would tell you which one you want.
Consider a so-called Schwartzian transform, which avoids recomputing the sort keys by temporarily associating them with the input items:
my #sorted =
map { $_->[0] }
sort { $a->[1] cmp $b->[1] or $a->[0] <=> $b->[0] }
map { [ $_ => sprintf "%.1s%08x", $_, length ] }
#source;
This is provided by List::UtilsBy::sort_by
use List::UtilsBy qw( sort_by );
my #sorted = sort_by { sprintf "%.1s%08x", $_, length } #source;
It's much the same as the Schwartzian Transform solutions others have suggested, but wrapped in a neat abstraction.
How do I dereference an array of arrays when passed to a function?
I am doing it like this:
my #a = {\#array1, \#array2, \#array3};
func(\#a);
func{
#b = #_;
#c = #{#b};
}
Actually I want the array #c should contain the addresses of #array1, #array2, and #array3.
my #a = {\#array1, \#array2, \#array3};
The above is an array with a single member -> a hash containing:
{ ''.\#array1 => \#array2, ''.\#array3 => undef }
Because as a key in the hash, Perl coerces the reference to #array1 into a string. And Perl allows a scalar hash reference to be assigned to an array, because it is "understood" that you want an array with the first element being the scalar you assigned to it.
You create an array of arrays, like so:
my #a = (\#array1, \#array2, \#array3);
And then in your function you would unpack them, like so:
sub func {
my $ref = shift;
foreach my $arr ( #$ref ) {
my #list_of_values = #$arr;
}
}
Or some variation thereof, like say a map would be the easiest expression:
my #list_of_entries = map { #$_ } #$ref;
In your example, #c as a list of addresses is simply the same thing as a properly constructed #a.
You may want to read perldoc perlreftut, perldoc perlref, and perldoc perldsc You can say:
sub func {
my $arrayref = shift;
for my $aref (#$arrayref) {
print join(", ", #$aref), "\n";
}
}
my #array1 = (1, 2, 3);
my #array2 = (4, 5, 6);
my #array3 = (7, 8, 9);
my #a = \(#array1, #array2, #array3);
func \#a;
or more compactly:
sub func {
my $arrayref = shift;
for my $aref (#$arrayref) {
print join(", ", #$aref), "\n";
}
}
func [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ];
Read the perlreftut documentation.
Edit: Others point out a good point I missed at first. In the initialization of #a, you probably meant either #a = (...) (create array containing references) or $arrayref = [...] (create reference to array), not {...} (create reference to hash). The rest of this post pretends you had the #a = (...) version.
Since you pass one argument (a reference to #a) to func, #_ is a list containing that one reference. You can get that reference and then dereference it by doing:
sub func {
my $arrayref = shift;
my #c = #{$arrayref};
}
Or in one line, it would look like:
sub func {
my #c = #{shift()};
}
(If you hadn't used the backslash in func(\#a), #_ would be equal to #a, the array of three references.)
The following function is designed to take either an array or an array reference and give back a sorted array of unique values. Undefined values are removed and HASH and GLOB are left as is.
#!/usr/bin/perl
use strict; use warnings;
my #one = qw / dog rat / ;
my #two = qw / dog mice / ;
my #tre = ( "And then they said it!", "No!?? ", );
open my $H, '<', $0 or die "unable to open $0 to read";
my $dog; # to show behavior with undefined value
my %hash; $hash{pig}{mouse}=55; # to show that it leaves HASH alone
my $rgx = '(?is)dog'; $rgx = qr/$rgx/; # included for kicks
my #whoo = (
'hey!',
$dog, # undefined
$rgx,
1, 2, 99, 999, 55.5, 3.1415926535,
%hash,
$H,
[ 1, 2,
[ 99, 55, \#tre, ],
3, ],
\#one, \#two,
[ 'fee', 'fie,' ,
[ 'dog', 'dog', 'mice', 'gopher', 'piranha', ],
[ 'dog', 'dog', 'mice', 'gopher', 'piranha', ],
],
[ 1, [ 1, 2222, ['no!', 'no...', 55, ], ], ],
[ [ [ 'Rat!', [ 'Non,', 'Tu es un rat!' , ], ], ], ],
'Hey!!',
0.0_1_0_1,
-33,
);
print join ( "\n",
recursively_dereference_sort_unique_array( [ 55, 9.000005555, ], #whoo, \#one, \#whoo, [ $H ], ),
"\n", );
close $H;
exit;
sub recursively_dereference_sort_unique_array
{
# recursively dereference array of arrays; return unique values sorted. Leave HASH and GLOB (filehandles) as they are.
# 2020v10v04vSunv12h20m15s
my $sb_name = (caller(0))[3];
#_ = grep defined, #_; #https://stackoverflow.com/questions/11122977/how-do-i-remove-all-undefs-from-array
my #redy = grep { !/^ARRAY\x28\w+\x29$/ } #_; # redy==the subset that is "ready"
my #noty = grep { /^ARRAY\x28\w+\x29$/ } #_; # noty==the subset that is "not yet"
my $countiter = 0;
while (1)
{
$countiter++;
die "$sb_name: are you in an infinite loop?" if ($countiter > 99);
my #next;
foreach my $refarray ( #noty )
{
my #tmparray = #$refarray;
push #next, #tmparray;
}
#next = grep defined, #next;
my #okay= grep { !/^ARRAY\x28\w+\x29$/ } #next;
#noty = grep { /^ARRAY\x28\w+\x29$/ } #next;
push #redy, #okay;
my %hash = map { $_ => 1 } #redy; # trick to get unique values
#redy = sort keys %hash;
return #redy unless (scalar #noty);
}
}
Should be
func {
$b = shift;
}
if you're passing in a reference. Hope that helps some.