Programatic access of a hash element - perl

Can anyone make this print "4" by replacing the PFM block??
my %hash;
$hash{1}{2}{3}=4;
my #key=qw(1 2 3);
my $key;
for(#key){PFM}
print $hash{$key}

my %hash;
$hash{1}{2}{3}=4;
my #key=qw(1 2 3);
my $data = \%hash;
for(#key){
$data = $data->{$_}
}
print $data

my $val = \%hash;
$val //= $val->{$_} for #key;
say $val;
or you could use Data::Diver
use Data::Diver qw( Dive );
say Dive(\%hash, #key);
Neither version will vivify anything if any part of the key doesn't exist.
If you want to set a value using such a key:
my $p = \\%hash;
$p = \( $$p->{$_} ) for #key;
$$p = 5;
or
use Data::Diver qw( DiveRef );
my $ref = DiveRef(\%hash, map \$_, #key);
$$ref = 5;
or
use Data::Diver qw( DiveVal );
DiveVal(\%hash, map \$_, #key) = 5;
(The map \$_, is required to make Data::Diver make hashes instead of arrays for numerical keys.)

Yes. But it's probably not what you wanted:
$key = "X";
$hash{X} = 4;
4 is not a value of the %hash originally:
my #fours = grep $_ == 4, values %hash;
print "[#fours]\n"; # prints '[]'

Related

How to split a string into multiple hash keys in perl

I have a series of strings for example
my #strings;
$strings[1] = 'foo/bar/some/more';
$strings[2] = 'also/some/stuff';
$strings[3] = 'this/can/have/way/too/many/substrings';
What I would like to do is to split these strings and store them in a hash as keys like this
my %hash;
$hash{foo}{bar}{some}{more} = 1;
$hash{also}{some}{stuff} = 1;
$hash{this}{can}{have}{way}{too}{many}{substrings} = 1;
I could go on and list my failed attempts, but I don't think they add to the value to the question, but I will mention one. Lets say I converted 'foo/bar/some/more' to '{foo}{bar}{some}{more}'. Could I somehow store that in a variable and do something like the following?
my $var = '{foo}{bar}{some}{more}';
$hash$var = 1;
NOTE: THIS DOESN'T WORK, but I hope it only doesn't due to a syntax error.
All help appreciated.
Identical logic to Shawn's answer. But I've hidden the clever hash-walking bit in a subroutine. And I've set the final value to 1 rather than an empty hash reference.
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use Data::Dumper;
my #keys = qw(
foo/bar/some/more
also/some/stuff
this/can/have/way/too/many/substrings
);
my %hash;
for (#keys) {
multilevel(\%hash, $_);
}
say Dumper \%hash;
sub multilevel {
my ($hashref, $string) = #_;
my $curr_ref = $hashref;
my #strings = split m[/], $string;
for (#strings[0 .. $#strings - 1]) {
$curr_ref->{$_} //= {};
$curr_ref = $curr_ref->{$_};
}
$curr_ref->{#strings[-1]} = 1;
}
You have to use hash references to walk down thru the list of keys.
use Data::Dumper;
my %hash = ();
while( my $string = <DATA> ){
chomp $string;
my #keys = split /\//, $string;
my $hash_ref = \%hash;
for my $key ( #keys ){
$hash_ref->{$key} = {};
$hash_ref = $hash_ref->{$key};
}
}
say Dumper \%hash;
__DATA__
foo/bar/some/more
also/some/stuff
this/can/have/way/too/many/substrings
Just use a library.
use Data::Diver qw(DiveVal);
my #strings = (
undef,
'foo/bar/some/more',
'also/some/stuff',
'this/can/have/way/too/many/substrings',
);
my %hash;
for my $index (1..3) {
my $root = {};
DiveVal($root, split '/', $strings[$index]) = 1;
%hash = (%hash, %$root);
}
__END__
(
also => {some => {stuff => 1}},
foo => {bar => {some => {more => 1}}},
this => {can => {have => {way => {too => {many => {substrings => 1}}}}}},
)
I took the easy way out w/'eval':
use Data::Dumper;
%hash = ();
#strings = ( 'this/is/a/path', 'and/another/path', 'and/one/final/path' );
foreach ( #strings ) {
s/\//\}\{/g;
$str = '{' . $_ . '}'; # version 2: remove this line, and then
eval( "\$hash$str = 1;" ); # eval( "\$hash{$_} = 1;" );
}
print Dumper( %hash )."\n";

Enter a value into a nested hash from a sequence of keys [duplicate]

This question already has an answer here:
Create a multidimesional key of hash from array?
(1 answer)
Closed 5 years ago.
I have a set of keys stored in an array that I would like to splice into a nested hash. For example I might have:
$hash->{$key1} = $value;
And what I would like to do is add in additional dimensions to the hash, eg:
my #array = ( $key2, $key3 ) ;
to give
$hash->{$key1}->{$key2}->{$key3} = $value;
I do not know beforehand how many keys will be in the array.
Is this what you mean?
use strict;
use warnings 'all';
my #keys = qw/ a b c /;
my $val = 99;
my $hash = { };
{
my $h = $hash;
$h = $h->{ shift #keys } = {} while #keys > 1;
$h->{ shift #keys } = $val;
}
use Data::Dumper;
print Dumper $hash;
output
$VAR1 = {
'a' => {
'b' => {
'c' => 99
}
}
};
use Data::Diver qw( DiveVal );
DiveVal($hash, map \$_, $key1, #array) = $value;
-or-
DiveVal($hash->{$key1}, map \$_, #array) = $value;
or
sub dive_val :lvalue { my $p = \shift; $p = \( $$p->{$_} ) for #_; $$p }
dive_val($hash, $key1, #array) = $value;
-or-
dive_val($hash->{$key1}, #array) = $value;

Making Perl's "specific declarations" as variable

I was looking for some sort of solution if the following setup can be interpreted as formal declaration of the variables , if possible.
What i have is :
my $str_1 = "{cow}" ;
my $str_2 = "{cow}{black}{tasty_milk}";
what i want is :
(Based on above variable string is it possible to initialize a hash directly,
something like :)
my %hash=();
$hash."*some operator* on $str_i" = 'Initialized' ;
This "some operator" should make $hash to recognize as hash as it was declared earlier. i.e Input specific hash initialization.
PS: I don't want to write a function that will work on the string and get all information to initialize the hash.
Say you had the following input instead:
my #path = qw( cow black tasty_milk );
Then you can use the following:
use Data::Diver qw( DiveVal );
DiveVal(\%hash, map \$_, #path) = 'value';
So, with Data::Diver, we get:
use Data::Diver qw( DiveVal );
$str =~ /^(?:\{\w+\})+\z/
or die("Unrecognized format");
my #path = $str =~ /(\w+)/g;
DiveVal(\%hash, map \$_, #path) = 'value';
Without a module:
sub dive_val :lvalue { my $p = \shift; $p = \( $$p->{$_} ) for #_; $$p }
$str =~ /^(?:\{\w+\})+\z/
or die("Unrecognized format");
my #path = $str =~ /(\w+)/g;
dive_val(\%hash, #path) = 'value';
Try the following with anonymous hash
use Data::Dumper;
use warnings;
use strict;
my $str_2 = "{cow}{black}{tasty_milk}";
my %hash;
my $ref =\%hash;
my $val;
my $lp = () = $str_2=~m/\}/g; #count the number of }
my $i = 1;
while($str_2=~m/\{(\w+)\}/g)
{
$val = $1;
last if ($lp == $i);
$ref->{$val} = {}; #make the anonymous hash
$ref = $ref->{$val}; #add the data into anonymous hash
$i++;
}
$ref->{$val} = "value"; #add the last value
print Dumper \%hash;

How to get array values in hash using map function in Perl

I have an array of elements combined with # which I wish to put in hash , first element of that array as key and rest as value after splitting of that array elements by #
But it is not happening.
Ex:
my #arr = qw(9093#AT#BP 8111#BR 7456#VD#AP 7786#WS#ER 9431#BP ) #thousand of data
What I want is
$hash{9093} = [AT,AP];
$hash{8111} = [BR]; and so on
How we can accomplish it using map function. Otherwise I need to use for loop but I wish to use map function.
my %hash = map { my ($k, #v) = split /#/; $k => \#v } #arr;
For comparison, the corresponding foreach loop follows:
my %hash;
for (#arr) {
my ($k, #v) = split /#/;
$hash{$k} = \#v;
}
Use split to split on '#', taking the first chunk as the key, and keeping the rest in an array. Then create a hash using the keys and references to the arrays.
use Data::Dumper;
my #arr = qw( 9093#AT#BP 8111#BR 7456#VD#AP 7786#WS#ER 9431#BP );
my %hash = map {
my ($key, #vals) = split '#', $_;
$key => \#vals;
} #arr;
print Dumper \%hash;
No effort shown in your question, but I am on a code freeze so I'll bite :)
A think that a for loop would be more idiomatic Perl here, process the elements one-by-one, split on # and then assign into your hash:
use strict;
use warnings;
use Data::Dumper;
my #arr = qw(9093#AT#BP 8111#BR 7456#VD#AP 7786#WS#ER 9431#BP );
my %h;
for my $elem ( #arr ) {
my ($key, #vals) = split /#/, $elem;
$h{$key} = \#vals;
}
print Dumper \%h;
That is easy:
%s = (map {split(/#/, $_, 2)} #arr);
Testing it:
$ cat 1.pl
my #arr = qw(9093#AT#BP 8111#BR 7456#VD#AP 7786#WS#ER 9431#BP );
%s = (map {split(/#/, $_, 2)} #arr);
foreach my $key ( keys %s )
{
print "key: $key, value: $s{$key}\n";
}
$ perl 1.pl
key: 7456, value: VD#AP
key: 8111, value: BR
key: 7786, value: WS#ER
key: 9431, value: BP
key: 9093, value: AT#BP
use strict;
use warnings;
use Data::Dumper;
my #arr = ('9093#AT#BP', '8111#BR', '7456#VD#AP', '7786#WS#ER', '9431#BP' );
my %h = map { map { splice(#$_, 0, 1), $_ } [ split /#/ ] } #arr;
print Dumper \%h;

Only create Perl hash key/value pair if value is defined

I wish to create a key/value pair in a hash only if I have a defined value to assign.
I am currently doing this:
$hash{key1} = $val1 if defined $val1;
which is OK - but can become annoying when $val1 is complicated. Is there a way that I can neatly get the same outcome without having to say $val1 twice? The test script below might help clarify what I am trying to achieve.
use strict;
use warnings;
use Test::More tests => 1;
my %hash;
my $val1 = undef; # Explicitly undef
my $val2 = 10;
$hash{key1} = $val1 if defined $val1;
$hash{key2} = $val2 if defined $val2;
my %expected = ('key2', 10);
is_deeply(\%hash, \%expected, 'Hashes compare');
If you have many such values to check you can use grep/list:
use strict;
use warnings;
my %hash;
my $val1 = undef;
my $val2 = 10;
$hash{$_->[0]} = $_->[1] for grep { defined $_->[1] }
['key1', $val1], ['key2', $val2];
Or you can filter the hash after populating it blindly:
$hash{key1} = $val1;
$hash{key2} = $val2;
%hash = map { $_, $hash{$_} } grep { defined $hash{$_} } keys %hash;
Write a subroutine.
set( \%hash, $key1, $val1 );
set( \&hash, $key2, $val2 );
sub set {
my $hash = shift;
my $key = shift;
my $val = shift;
$hash->{$key} = $val if defined $val;
}