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";
Related
Given a string of Key:Value pairs, I want to create a lookup hash but with lowercase values for the keys. I can do so with this code
my $a="KEY1|Value1|kEy2|Value2|KeY3|Value3";
my #a = split '\|', $a;
my %b = map { $a[$_] = ( !($_ % 2) ? lc($a[$_]) : $a[$_]) } 0 .. $#a ;
The resulting Hash would look like this Dumper output:
$VAR1 = {
'key3' => 'Value3',
'key2' => 'Value2',
'key1' => 'Value1'
};
Would it be possible to directly create hash %b without using temporary array #a or is there a more efficient way to achieve the same result?
Edit: I forgot to mention that I cannot use external modules for this. It needs to be basic Perl.
You can use pairmap from List::Util to do this without an intermediate array at all.
use strict;
use warnings;
use List::Util 1.29 'pairmap';
my $str="KEY1|Value1|kEy2|Value2|KeY3|Value3";
my %hash = pairmap { lc($a) => $b } split /\|/, $str;
Note: you should never use $a or $b outside of sort (or List::Util pair function) blocks. They are special global variables for sort, and just declaring my $a in a scope can break all sorts (and List::Util pair functions) in that scope. An easy solution is to immediately replace them with $x and $y whenever you find yourself starting to use them as example variables.
Since the key-value pair has to be around the | you can use a regex
my $v = "KEY1|Value1|kEy2|Value2|KeY3|Value3";
my %h = split /\|/, $v =~ s/([^|]+) \| ([^|]+)/lc($1).q(|).$2/xger;
use strict;
use warnings;
use Data::Dumper;
my $i;
my %hash = map { $i++ % 2 ? $_ : lc } split(/\|/, 'KEY1|Value1|kEy2|Value2|KeY3|Value3');
print Dumper(\%hash);
Output:
$VAR1 = {
'key1' => 'Value1',
'key2' => 'Value2',
'key3' => 'Value3'
};
For fun, here are two additional approaches.
A cheaper one than the original (since the elements are aliased rather than copied into #_):
my %hash = sub { map { $_ % 2 ? $_[$_] : lc($_[$_]) } 0..$#_ }->( ... );
A more expensive one than the original:
my %hash = ...;
#hash{ map lc, keys(%hash) } = delete( #hash{ keys(%hash) } );
More possible solutions using regexes to do all the work, but not very pretty unless you really like regex:
use strict;
use warnings;
my $str="KEY1|Value1|kEy2|Value2|KeY3|Value3";
my %hash;
my $copy = $str;
$hash{lc $1} = $2 while $copy =~ s/^([^|]*)\|([^|]*)\|?//;
use strict;
use warnings;
my $str="KEY1|Value1|kEy2|Value2|KeY3|Value3";
my %hash;
$hash{lc $1} = $2 while $str =~ m/\G([^|]*)\|([^|]*)\|?/g;
use strict;
use warnings;
my $str="KEY1|Value1|kEy2|Value2|KeY3|Value3";
my %hash = map { my ($k, $v) = split /\|/, $_, 2; (lc($k) => $v) }
$str =~ m/([^|]*\|[^|]*)\|?/g;
Here's a solution that avoids mutating the input string, constructing a new string of the same length as the input string, or creating an intermediate array in memory.
The solution here changes the split into looping over a match statement.
#! /usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
my $a="KEY1|Value1|kEy2|Value2|KeY3|Value3";
sub normalize_alist_opt {
my ($input) = #_;
my %c;
my $last_key;
while ($input =~ m/([^|]*(\||\z)?)/g) {
my $s = $1;
next unless $s ne '';
$s =~ s/\|\z//g;
if (defined $last_key) {
$c{ lc($last_key) } = $s;
$last_key = undef;
} else {
$last_key = $s;
}
}
return \%c;
}
print Dumper(normalize_alist_opt($a));
A potential solution that operates over the split directly. Perl might recognize and optimize the special case. Although based on discussions here and here, I'm not sure.
sub normalize_alist {
my ($input) = #_;
my %c;
my $last_key;
foreach my $s (split /\|/, $input) {
if (defined $last_key) {
$c{ lc($last_key) } = $s;
$last_key = undef;
} else {
$last_key = $s;
}
}
return \%c;
}
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;
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;
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;
}
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 '[]'