Adding lists of key/values to an existing hash - perl

I can initialize a hash from a list, e.g:
my %hash = (1..10);
my %hash2 = split /[\n=]/;
Is there a better (briefer) way to add lists of new key/values to a hash than using temporary variables?
while (<>) {
my ($k, $v) = split /=/, $_, 2;
$hash{$k} = $v;
}

It's possible, yes. It's rather like adding some numbers to an existing total - you might accomplish that like this:
my $total = 1 + 2 + 3; # here's the existing total
# let's add some additional numbers
$total = $total + 4 + 5; # the $total variable appears both
# left and right of the equals sign
You can do similar when inserting values to an existing hash...
my %hash = (a => 1, b => 2); # here's the existing hash.
# let's add some additional values
$_ = "c=3" . "\n" . "d=4";
%hash = (%hash, split /[\n=]/); # the %hash variable appears both
# left and right of the equals sign
With addition, there's a nice shortcut for this:
$total += 4 + 5;
With inserting values to a hash, there's no such shortcut.

Maybe I'm wrong but the next
use 5.014;
use warnings;
use Data::Dumper;
#create a hash
my %hash = map { "oldvar" . $_ => "oldval" . $_ } (1..3);
#add to the hash
%hash = (%hash, map {split /[=\n]/} <DATA>);
say Dumper \%hash;
__DATA__
var1=val1
var2=val2
var3=val3
prints
$VAR1 = {
'oldvar1' => 'oldval1',
'oldvar2' => 'oldval2',
'var1' => 'val1',
'var3' => 'val3',
'oldvar3' => 'oldval3',
'var2' => 'val2'
};

Related

How to ignore hash's key sequence in perl

I has a file that contain information like below
1:2=14
3:4=15
2:1=16
4:3=17
I would like to create a hash that treat key {1:2} same with key {2:1}
$hash{1:2} = $hash{2:1}
so when i print all element in $hash{1:2} or $hash{2:1} it give me both 14 and 16 as the result. Is it a simple way to do that?
Below are my planning to create the key for hash
for ( my $i = 0; $i <=$#file ; $i++) {
my $line = $file[$i];
my #key = split ("=",$line);
my $hash{$key[0]} = $key[1];
}
There is no built-in way to do that. If your keys follow the same pattern, you can wrap it in a function, then compute all keys that fit your algorithm and get the values.
my %hash = (
'1:2' => 14,
'3:4' => 15,
'2:1' => 16,
'4:3' => 17,
);
sub get_elements {
my ($key) = #_;
return $hash{$key}, $hash{reverse $key}; # or $key =~ s/(\d+):(\d+)/$2:$1/r
}
print join ' ', get_elements('1:2');
This uses string reverse, and obviously only works if the parts of the key are only one digit. If you can have numbers greater than 9 you will have to split the string and re-assemble it, or use a substitution to switch them around. My example uses the /r modifier, which needs Perl 5.14 or higher.
If you want to however build a data structure when reading your file that takes care of this automatically, you can do that too. Use an array reference instead of the simple values inside your hash, and assign the same reference to all keys that you want to be equal.
use strict;
use warnings;
use feature 'say';
use Data::Dumper;
my %hash;
while (my $line = <DATA>) {
chomp $line;
my ($key, $value) = split /=/, $line;
my ($left, $right) = split /:/, $key;
# $hash{$key} //= ( $hash{"$right:$left"} // [] ); # needs 5.10
$hash{$key} = ( $hash{"$right:$left"} || [] )
unless exists $hash{$key};
push #{ $hash{$key} }, $value;
}
say "#{ $hash{'1:2'} }";
say "#{ $hash{'2:1'} }";
print Dumper \%hash;
__DATA__
1:2=14
3:4=15
2:1=16
4:3=17
The output of this code is
14 16
14 16
The Data::Dumper structure looks like this, which explains that both keys 2:1 and 1:2 point to the same array reference. That means that if you push another value into one of them, it will end up in the other as well, because they are actually the same thing.
$VAR1 = {
'4:3' => [
'15',
'17'
],
'3:4' => $VAR1->{'4:3'},
'2:1' => [
'14',
'16'
],
'1:2' => $VAR1->{'2:1'}
};
The only downside of this is that you cannot get the order of the elements back. This data structure looses the knowledge that 1:2 had the value 14 and 2:1 had 16 initially. If you want 2:1 to output 16 14 this will not work.

Split hash in hash of array

I have three hashes that I would like to explode each one into a Hash of Arrays. Each one could be passed into a subroutine with a split function, but this requires the same subroutine to be called 3 times. I have tried to iterate through each hash and them split it but without success. Is it possible to do this without calling a subroutine? The code I have tried is:
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my %hash1 = (
A => "AAAAAAAAAA",
B => "AAAAANAAAA",
C => "AAAAAXAAAA",
D => "AAXAAAAAAA",
E => "AAAAAAAAAX",
);
my %hash2 = (
F => "BBBBBBBBBB",
G => "BBBBBBBBBB",
H => "BBXBBBBBBB",
I => "BBBBBBBBBB",
J => "AAAAANAAAA",
);
foreach my $ref ( \%hash1, \%hash2 ) {
while ( my ($key, $value) = each %$ref) {
#{ $ref->{$key} } = split (//, $value );
}
}
print Dumper %hash1;
but gives the warning:
Can't use string ("AAAAAAAAAA") as an ARRAY ref while "strict refs" in use
Thanks in advance.
Your mistake is that you are taking the value of the key $ref->{$key} which is AAAAAAA etc, and using it as a reference with the #{ ... } braces. You need to assign to $ref->{$key} directly.
Since you are splitting the original value into a list, you need an array to store it. You can do this either by using a named array, lexically scoped, or an anonymous array. My choice would be to use an anonymous array, but a named array might appear more readable:
foreach my $ref ( \%hash1, \%hash2 ) {
while ( my ($key, $value) = each %$ref) {
$ref->{$key} = [ split (//, $value ) ]; # using anonymous array
# my #list = split (//, $value );
# $ref->{$key} = \#list; # using named array
}
}
This process would overwrite the original values, but I assume that is what you want.
A more direct way to achieve the same thing would be this:
$_ = [ split // ] for values %hash1, values %hash2;
Here we are (ab)using the fact that the elements in a for loop are aliased to the original variables, and simply overwriting the values the same way we did above.
If the compressed format is unwanted, a more verbose alternative would be:
for my $value (values %hash1, values %hash2) {
$value = [ split //, $value ];
}
Assuming I correctly understood what you want to do, you can use hash slicing and do something like this:
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my %hash1 = (
A => "AAAAAAAAAA",
B => "AAAAANAAAA",
C => "AAAAAXAAAA",
D => "AAXAAAAAAA",
E => "AAAAAAAAAX",
);
my %hash2 = (
F => "BBBBBBBBBB",
G => "BBBBBBBBBB",
H => "BBXBBBBBBB",
I => "BBBBBBBBBB",
J => "AAAAANAAAA",
);
foreach my $ref ( \%hash1, \%hash2 ) {
my #keys = keys %$ref;
#$ref{#keys} = map { [split //, $ref->{$_}] } #keys;
}
print Dumper \%hash1;
print Dumper \%hash2;
This is like writing:
foreach my $ref ( \%hash1, \%hash2 ) {
my #keys = keys %$ref;
foreach my $key (#keys) {
my #arr = split //, $ref->{$key};
$ref->{$key} = \#arr;
}
}
just simpler.

cant get array of hashes in perl

I have the Employees CSV Data and i
try to insert each employee hash in to an array
open($empOutFh,">empOut.txt")
$hash= [];
while(<$empFh>) {
#columnNames = split /,/, $_ if $.==1;
#columnValues = split /,/, $_;
%row = map{$_=>shift #columnValues}#columnNames;
push #$hash,\%row;
}
print Dumper($hash);
I am getting the output has
$VAR1 = [
{
'emp_no' => '11000',
'hire_date
' => '1988-08-20
',
'birth_date' => '1960-09-12',
'gender' => 'M',
'last_name' => 'Bonifati',
'first_name' => 'Alain'
},
$VAR1->[0],
$VAR1->[0],
$VAR1->[0]
]
But when i am try to print each row it showing different row hash for each time
The problem is that you're using a single hash %row, so \%row is always referring to the same hash. Every time you assign to %row, you're not setting it to a new hash, you're just clearing out the same hash and repopulating it (thereby affecting, indirectly, every element of your array).
To fix this, you need to create a new hash in each loop iteration. The minimal change to your code would be to declare %row as a lexical variable with local scope, by using the my operator:
my %row = map { $_ => shift #columnValues } #columnNames;
push #$hash, \%row;
Another option is to eliminate the intermediate variable entirely, and just generate a reference to a new anonymous hash on each pass:
push #$hash, { map { $_ => shift #columnValues } #columnNames };
If you can't get a map to work properly, use a foreach loop instead. Being able to maintain the code is more important than being clever.
#!/usr/bin/env perl
use strict;
use warnings;
# --------------------------------------
use Data::Dumper;
# Make Data::Dumper pretty
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Indent = 1;
# Set maximum depth for Data::Dumper, zero means unlimited
local $Data::Dumper::Maxdepth = 0;
# --------------------------------------
# open($empOutFh,">empOut.txt")
my $emp_file = 'empOut.txt';
open my $emp_out_fh, '>', $emp_file or die "could not open $emp_file: $!\n";
# $hash= [];
my #emps = ();
my #columnNames = ();
# while(<$empFh>) {
while( my $line = <$empFh> ){
chomp;
# #columnNames = split /,/, $_ if $.==1;
if( $. == 1 ){
#columnNames = split /,/, $line;
next;
}
# #columnValues = split /,/, $_;
my #columnValues = split /,/, $line;
my %row = ();
# %row = map{$_=>shift #columnValues}#columnNames;
for my $i ( 0 .. $#columnNames ){
$row{$columnNames[$i]} = $columnValues[$i];
}
# push #$hash,\%row;
push #emps, \%row;
# }
}
# print Dumper($hash);
print Dumper \#emps;

sum hash of hash values using perl

I have a Perl script that parses an Excel file and does the following : It counts for each value in column A, the number of elements it has in column B, the script looks like this :
use strict;
use warnings;
use Spreadsheet::XLSX;
use Data::Dumper;
use List::Util qw( sum );
my $col1 = 0;
my %hash;
my $excel = Spreadsheet::XLSX->new('inout_chartdata_ronald.xlsx');
my $sheet = ${ $excel->{Worksheet} }[0];
$sheet->{MaxRow} ||= $sheet->{MinRow};
my $count = 0;
# Iterate through each row
foreach my $row ( $sheet->{MinRow}+1 .. $sheet->{MaxRow} ) {
# The cell in column 1
my $cell = $sheet->{Cells}[$row][$col1];
if ($cell) {
# The adjacent cell in column 2
my $adjacentCell = $sheet->{Cells}[$row][ $col1 + 1 ];
# Use a hash of hashes
$hash{ $cell->{Val} }{ $adjacentCell->{Val} }++;
}
}
print "\n", Dumper \%hash;
The output looks like this :
$VAR1 = {
'13' => {
'klm' => 1,
'hij' => 2,
'lkm' => 4,
},
'12' => {
'abc' => 2,
'efg' => 2
}
};
This works great, my question is : How can I access the elements of this output $VAR1 in order to do : for value 13, klm + hij = 3 and get a final output like this :
$VAR1 = {
'13' => {
'somename' => 3,
'lkm' => 4,
},
'12' => {
'abc' => 2,
'efg' => 2
}
};
So basically what I want to do is loop through my final hash of hashes and access its specific elements based on a unique key and finally do their sum.
Any help would be appreciated.
Thanks
I used #do_sum to indicate what changes you want to make. The new key is hardcoded in the script. Note that the new key is not created if no key exists in the subhash (the $found flag).
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my %hash = (
'13' => {
'klm' => 1,
'hij' => 2,
'lkm' => 4,
},
'12' => {
'abc' => 2,
'efg' => 2
}
);
my #do_sum = qw(klm hij);
for my $num (keys %hash) {
my $found;
my $sum = 0;
for my $key (#do_sum) {
next unless exists $hash{$num}{$key};
$sum += $hash{$num}{$key};
delete $hash{$num}{$key};
$found = 1;
}
$hash{$num}{somename} = $sum if $found;
}
print Dumper \%hash;
It sounds like you need to learn about Perl References, and maybe Perl Objects which are just a nice way to deal with references.
As you know, Perl has three basic data-structures:
Scalars ($foo)
Arrays (#foo)
Hashes (%foo)
The problem is that these data structures can only contain scalar data. That is, each element in an array can hold a single value or each key in a hash can hold a single value.
In your case %hash is a Hash where each entry in the hash references another hash. For example:
Your %hash has an entry in it with a key of 13. This doesn't contain a scalar value, but a references to another hash with three keys in it: klm, hij, and lkm. YOu can reference this via this syntax:
${ hash{13} }{klm} = 1
${ hash{13} }{hij} = 2
${ hash{13} }{lkm} = 4
The curly braces may or may not be necessary. However, %{ hash{13} } references that hash contained in $hash{13}, so I can now reference the keys of that hash. You can imagine this getting more complex as you talk about hashes of hashes of arrays of hashes of arrays. Fortunately, Perl includes an easier syntax:
$hash{13}->{klm} = 1
%hash{13}->{hij} = 2
%hash{13}->{lkm} = 4
Read up about hashes and how to manipulate them. After you get comfortable with this, you can start working on learning about Object Oriented Perl which handles references in a safer manner.

Tie::IxHash ordered associative arrays in Hash of Hashes?

How can I preserve the order in which the hash elements were added
FOR THE SECOND VAR ?
( Hash of Hashes )
For example:
use Tie::IxHash;
my %hash;
tie %hash, "Tie::IxHash";
for my $num (0 .. 5){
$hash{"FirstVal$num"}++;
}
for my $num (0 .. 5){
$hash{"FirstValFIXED"}{"SecondVal$num"}++;
}
print Dumper(%hash);
When dumping out the result, $VAR14 didn't preserve the insertion order:
$VAR1 = 'FirstVal0';
$VAR2 = 1;
$VAR3 = 'FirstVal1';
$VAR4 = 1;
$VAR5 = 'FirstVal2';
$VAR6 = 1;
$VAR7 = 'FirstVal3';
$VAR8 = 1;
$VAR9 = 'FirstVal4';
$VAR10 = 1;
$VAR11 = 'FirstVal5';
$VAR12 = 1;
$VAR13 = 'FirstValFIXED';
$VAR14 = {
'SecondVal5' => 1,
'SecondVal4' => 1,
'SecondVal2' => 1,
'SecondVal1' => 1,
'SecondVal3' => 1,
'SecondVal0' => 1
};
I know I can trick that example with some sort operation but in my real problem the elements are not numbered or can't be ordered some how.
Is there any simple function/operation for hash multi level order insertion ?
Thanks,
Yodar.
Look at Tie::Autotie. It automatically ties new hashes created by autovivification. The perldoc page shows an example using Tie::IxHash.
You need an extra "\", as below.
print Dumper(\%hash);
Do you mean hash of hashes? You need to tie to Tie::IxHash every value of outer hash.
use strict;
use warnings;
use Tie::IxHash;
my $hash={};
my $t = tie(%$hash, 'Tie::IxHash', 'a' => 1, 'b' => 2);
%$hash = (first => 1, second => 2, third => 3);
$hash->{fourth} = 4;
print join(', ',keys %$hash),"\n";
my %new_hash=('test'=>$hash);
$new_hash{'test'}{fifth} = 5;
print join(', ',keys %{$new_hash{'test'}}),"\n";
$new_hash{'test'}{fifth}++;
print join(', ',values %{$new_hash{'test'}}),"\n";
foreach my $sortline (sort {$a<=>$b} keys %{$hash->{"first_field"}}){
my $name;
# Soultion to touch a Key with keys within it:
#---------------------------------------------
foreach my $subkey (keys %{$hash->{"first_field"}->{$sortline}})
{$name = $subkey;}
#---------------------------------------------
}
This useful answer helped me.