I have a bunch of Hashes inside of an array. When checking my keys and values I get the expected output except for some special cases as they refer to more Arrays/Hashes.
Think of something like this:
#AoH = ( { 'husband' => "homer", 'wife' => "marge" },
{ 'people' => [{'Bob'=> 24, 'Lukas'=> 37}] },
{ 'vegetables' => { 'tomato' => "red", 'carrot' => "orange"} });
My function iterates through the array and displays my keys and values as in the following:
sub function(...){
print "$key => $value\n";
}
husband => homer
wife => marge
people => ARRAY(0x6b0d80)
Bob => 24
Lukas => 37
vegetables => HASH(0x2570d38)
tomato => red
carrot => orange
Now I want to access my keys and values, but when getting something like ARRAY or HASH as value, I want to disregard that hash and not print it.
Is there some kind of way to only access Values with type scalar?
So far I tried this:
if ($value eq 'ARRAY') {
}
elsif ($value eq ref {}) {
}
else {
print "$key => $value\n";
}
But, it ends up printing exactly the same as above and does not disregard the other data structures.
For an arbitrary data structure like yours, you can use Data::Traverse:
use warnings;
use strict;
use Data::Traverse qw(traverse);
my #AoH = ( { 'husband' => "homer", 'wife' => "marge" },
{ 'people' => [{'Bob'=> 24, 'Lukas'=> 37}] },
{ 'vegetables' => { 'tomato' => "red", 'carrot' => "orange"} });
traverse { print "$a => $b\n" if /HASH/ } \#AoH;
Output:
wife => marge
husband => homer
Bob => 24
Lukas => 37
carrot => orange
tomato => red
Following demo code does not utilize external modules, provided for educational purpose.
use strict;
use warnings;
use feature 'say';
my #AoH = ( { 'husband' => "homer", 'wife' => "marge" },
{ 'people' => [{'Bob'=> 24, 'Lukas'=> 37}] },
{ 'vegetables' => { 'tomato' => "red", 'carrot' => "orange"} });
drill_in( \#AoH );
sub drill_in {
my $data = shift;
if( ref $data eq 'ARRAY' ) {
drill_in($_) for #$data;
} elsif ( ref $data eq 'HASH' ) {
while( my($k, $v ) = each %{$data} ) {
(ref $v eq 'ARRAY' or ref $v eq 'HASH') ? drill_in($v) : say "$k => $v";
}
}
}
Output
husband => homer
wife => marge
Lukas => 37
Bob => 24
tomato => red
carrot => orange
Related
I have a list like this:
my $myV3VersionOfData = ["ZG","ZB","CXLDN",...];
and I want to convert it into a dictionary like this:
my $entries = {
'ZG' => {
'value' => 'ZG'
},
'ZB' => {
'value' => 'ZB'
},
'CXLDN' => {
'value' => 'CXLDN'
},
...
};
I tried this so far, but it doesn't work and gives me an error:
Can't use string ("ZG") as a HASH ref while "strict refs" in use at..
I understand this is occurring since I'm trying to assign the key value from the list, but how do I convert this list into a dictionary shown above?
my %genericHash;
for my $entry (#$myV3VersionOfData) {
$genericHash{ $entry->{key} } = $entry->{value};
}
How can I achieve this? I am new to Perl, and I have tried a bunch of things but it doesn't seem to work. Can anyone please help with this?
You were close. Here is one way to create a hash reference from an array reference:
use warnings;
use strict;
use Data::Dumper;
$Data::Dumper::Sortkeys=1;
my $myV3VersionOfData = ["ZG","ZB","CXLDN"];
my $entries;
for (#{ $myV3VersionOfData }) {
$entries->{$_} = {value => $_};
}
print Dumper($entries);
Output:
$VAR1 = {
'CXLDN' => {
'value' => 'CXLDN'
},
'ZB' => {
'value' => 'ZB'
},
'ZG' => {
'value' => 'ZG'
}
};
If you want to go through every element, to compute a new element, then you can use the map function. As map, can return multiple values, you return two values for each entry. And those can be converted to a hash.
my $array = ["ZG","ZB","CXLDN"];
my %hash = map { $_ => { value => $_ } } #$array;
my $hashref = { map { $_ => { value => $_ } } #$array };
Here's how I've done it for over 10 years.
#! /usr/bin/perl
use warnings;
use strict;
use Data::Dumper qw(Dumper);
my %entries;
my #myV3VersionOfData = ("ZG","ZB","CXLDN");
foreach (#myV3VersionOfData) {
$entries{$_}{'value'} = $_;
}
print Dumper \%entries;
We want
'ZG' => { 'value' => 'ZG' } # Copied literally from the Question
But the ZG part is variable, so we use
$_ => { 'value' => $_ }
Now loop!
my %genericHash = map { $_ => { 'value' => $_ } } #$myV3VersionOfData;
my $entries = { map { $_ => { 'value' => $_ } } #$myV3VersionOfData };
It's not clear which one you want.
Of course, it could also be done using a foreach loop.
my %genericHash;
for (#$myV3VersionOfData) {
$genericHash{$_} = { 'value' => $_ };
}
Please study following code snippet for compliance with your problem.
use strict;
use warnings;
use feature 'say';
use Data::Dumper;
my $myV3VersionOfData = ['ZG','ZB','CXLDN'];
my $hashref;
$hashref->{$_}{value} = $_ for #$myV3VersionOfData;
say Dumper($hashref);
Output
$VAR1 = {
'CXLDN' => {
'value' => 'CXLDN'
},
'ZB' => {
'value' => 'ZB'
},
'ZG' => {
'value' => 'ZG'
}
};
I'm parsing an excel spreadsheet and trying to combine data rows by id across tables and files. This is a condensed, simplified version of what I have. With a PHP/JS background, I prefer conceptualizing hashes as objects, so %aoo means array of objects instead of array of hashes...
#!/usr/bin/env perl
use v5.10.0;
use strict;
use warnings;
use Data::Dump;
use Data::Dumper;
# Array of objects
# Each object is a row from a table
my $aoo1 = [
{
"id" => 1,
"name" => "Dan",
"team" => "red"
},
{
"id" => 2,
"name" => "Arnold",
"team" => "red"
},
{
"id" => 3,
"name" => "Kristen",
"team" => "red"
}
];
my #aoo2 = (
{
"id" => 1,
"position" => "web developer",
},
{
"id" => 2,
"position" => "CEO",
},
{
"id" => 3,
"position" => "Secretary",
}
);
my #aoo3 = (
{
"id" => 1,
"tenure" => "1yr",
},
{
"id" => 2,
"tenure" => "25yr",
},
{
"id" => 3,
"tenure" => "5yr",
}
);
# object of arrays
# each property is a table name from spreadsheet
my %ooa;
%ooa = (
"People List" => $aoo1,
"Position List" => \#aoo2,
"Tenure List" => \#aoo3
);
# dd \%ooa;
while (my ($list_name, $aoo) = each %ooa)
{
# $aoo reftype is array | [ %object, %object, %object ]
# Do something to look into other objects for same id...
}
I want to be able to create a new object for each unique row in the file, so I can filter the values and then write it to a CSV file.
Ex. of the end result
%complete_row = (
'id' => 1,
'name' => 'Dan',
'team' => 'red',
'position => 'Web Dev',
'tenure' => '1yr'
);
Put the 2nd and 3rd arrays into hashes mapping ID to the hash. Then loop through the people and use the ID to get the data from position and tenure hashes.
use strict;
use warnings;
use Data::Dumper;
my $people = [
{
id => 1,
name => "Dan",
team => "red"
},
{
id => 2,
name => "Arnold",
team => "red"
},
{
id => 3,
name => "Kristen",
team => "red"
}
];
my $positions = [
{
id => 1,
position => "web developer",
},
{
id => 2,
position => "CEO",
},
{
id => 3,
position => "Secretary",
}
];
my $tenures = [
{
id => 1,
tenure => "1yr",
},
{
id => 2,
tenure => "25yr",
},
{
id => 3,
tenure => "5yr",
}
];
# hash each by ID
my %position_hash = map { $_->{id} => $_ } #$positions;
my %tenure_hash = map { $_->{id} => $_ } #$tenures;
# combine
my $complete = [];
foreach my $person (#$people) {
my $id = $person->{id};
my $complete_row = {
%$person,
position => $position_hash{$id}->{position},
tenure => $tenure_hash{$id}->{tenure},
};
push #$complete, $complete_row
}
print "complete = " . Dumper($complete);
This should work:
my %newHash;
foreach my $arrRef(map {$ooa{$_}} keys %ooa) { #reading all values of ooa hash, each value is an array ref
foreach my $hashRef(#$arrRef) { #reading each array element, each array element is a hash ref
foreach my $key(keys %{$hashRef}) { #reading all keys of each internal hash
$newHash{$hashRef->{'id'}}{$key} = $hashRef->{$key}; #building new hash of hashes with id as key and value as hash ref
}
}
}
my #newArray = map {$newHash{$_}} keys %newHash; #converting hash of hashes into array of hashes
I am facing an issue with sorting an array of hashes; hashes having alphanumeric values by which I need to sort.
Refer to this question! My question is an extension of this problem.
I tried the solution in the above question but didn't get the required output.
$arr_ref = [
{ brand => "A.1", PO => "1.a", supplier => "X" },
{ brand => "A.2", PO => "2.a", supplier => "X" },
{ brand => "B.1", PO => "1.b", supplier => "X" },
{ brand => "B.2", PO => "2.b", supplier => "X" },
{ brand => "B.3", PO => "1.c", supplier => "Y" },
]
I need to sort by Brand or PO.
#sort the array reference and place the array back into the standard_set
$arr_ref = [sort by_brand #$arr_ref];
sub by_brand {
$a->{brand} cmp $b->{brand}
}
Complexity is the key; can start with numeric or alphabetic character.
The Brand or PO can be of different size, as well. The delimiter may be a dot or hypen.
Can we solve this depending on the input received in $arr_ref?
You were really close. You just need to remove the [ and ] brackets and dereference the array you want to assign to #$array_ref = ....
use strict;
use warnings;
my $arr_ref = [
{ brand => "B.3", PO => "1.c", supplier => "Y" },
{ brand => "B.2", PO => "2.b", supplier => "X" },
{ brand => "B.1", PO => "1.b", supplier => "X" },
{ brand => "A.2", PO => "2.a", supplier => "X" },
{ brand => "A.1", PO => "1.a", supplier => "X" },
];
my #sorted = sort { $a->{brand} cmp $b->{brand} } #$arr_ref;
use Data::Dump;
dd #sorted;
Output:
(
{ brand => "A.1", PO => "1.a", supplier => "X" },
{ brand => "A.2", PO => "2.a", supplier => "X" },
{ brand => "B.1", PO => "1.b", supplier => "X" },
{ brand => "B.2", PO => "2.b", supplier => "X" },
{ brand => "B.3", PO => "1.c", supplier => "Y" },
)
Try this sort method:
sub by_brand_or_po {
my ($a_ba, $a_bn) = split /\.|-/, $a->{brand};
my ($b_ba, $b_bn) = split /\.|-/, $b->{brand};
my ($a_pa, $a_pn) = split /\.|-/, $a->{PO};
my ($b_pa, $b_pn) = split /\.|-/, $b->{PO};
return( $a_ba cmp $b_ba or $a_bn <=> $b_bn or
$a_pa cmp $b_pa or $a_pn <=> $b_pn );
}
It prioritizes the brand alpha over numeric over PO alpha over PO numeric, and will split over a dot or hyphen.
Sort it using Schwartzian transform and correct comparator for each field.
use strict;
use warnings;
use Data::Dumper;
my $arr_ref = [
{ brand => "A.1", PO => "1.a", supplier => "X" },
{ brand => "A.2", PO => "2.a", supplier => "X" },
{ brand => "B.1", PO => "1.b", supplier => "X" },
{ brand => "B.2", PO => "2.b", supplier => "X" },
{ brand => "B.3", PO => "1.c", supplier => "Y" },
];
my #sorted = map $_->[0], sort {
$a->[1][0] cmp $b->[1][0]
or $a->[1][1] <=> $b->[1][1]
or $a->[1][2] <=> $b->[1][2]
or $a->[1][3] cmp $b->[1][3]
} map [ $_, [ map split( /[.-]/, $_, 2 ), #$_{qw(brand PO)} ] ],
#$arr_ref;
print Dumper( \#sorted );
Try use brand "A.10" to spot the difference.
use Sort::Key::Natural!
use Sort::Key::Natural qw(natkeysort);
my #sorted_by_brand = natkeysort { $_->{brand} } #data;
my #sorted_by_po = natkeysort { $_->{po} } #data;
I have the following hashref of hashrefs structure:
$hashref = {
user1 => {
key1 => "unique_value1",
...
key99 => "value1_99"
},
...
user26 => {
key1 => "unique_value2",
...
key99 => "value1_99"
},
user99 => {
key1 => "unique_value1",
...
key99 => "value99_99"
},
};
What I want out:
$hashref = {
"unique_value1" => ["user1","user99"],
"unique_value2" => ["user26"]
};
I've historically built the inverted map on create, but I'm getting lazy.
Any one line sugar I could use for this?
Thank you.
One way:
my $h;
push (#{$h->{$hashref->{$_}{key1}}}, $_) for keys %$hashref;
my %users_by_uval;
for my $user_id (%$users) {
push #{ $users_by_uval{ $users->{$user_id}{key1} } }, $user_id;
}
How come I never see examples like these where you declare the hash, and then put then inside another hash?
my %hash1={};
$hash1{'key1'}='1-111';
$hash1{'key2'}='1-222';
$hash1{'key3'}='1-333';
my %hash2={};
$hash2{'key1'}='2-111';
$hash2{'key2'}='2-222';
$hash2{'key3'}='2-333';
my %main_hash1={%hash1, %hash2};
I've only seen examples like these where they put the hashes inside the hash, instead of a variable for the hash:
my %main_hash2=( 'hash1' => {
'key1' => '1-111',
'key2' => '1-222',
'key3' => '1-333'
},
'hash2' => {
'key1' => '2-111',
'key2' => '2-222',
'key3' => '2-333'
}
);
(similar with arrays also)
You can't store a hash in a hash, you can store a hashref in a hash though:
my %main_hash1 = ( hash1 => \%hash1, hash2 => \%hash2 );
The same goes with arrays:
my #main_array1 = ( \#array1, \#array2 );
And with mixes:
my #array_of_hrefs = ( \%hash1, \%hash2 );
my %hash_of_arefs = ( arr1 => \#arr1, arr2 => \#arr2 );
This is done all the time; I don't know why you haven't seen it and I doubt anyone on SO would know that answer.
Also, this does not initialize a hash:
my %hash1={}; ## should be my %hash1; or my %hash1 = ();
See the following example :
The Perl code :
my %hash1;
$hash1{'key1'}='1-111';
$hash1{'key2'}='1-222';
$hash1{'key3'}='1-333';
my %hash2;
$hash2{'key1'}='2-111';
$hash2{'key2'}='2-222';
$hash2{'key3'}='2-333';
my %main_hash = ( hash1 => \%hash1, hash2 => \%hash2 );
use Data::Dumper;
print Dumper %main_hash;
The output :
$VAR1 = 'hash2';
$VAR2 = {
'key2' => '2-222',
'key1' => '2-111',
'key3' => '2-333'
};
$VAR3 = 'hash1';
$VAR4 = {
'key2' => '1-222',
'key1' => '1-111',
'key3' => '1-333'
};
That use references, see http://perldoc.perl.org/perlreftut.html & if needed : http://perldoc.perl.org/perlref.html
There's a ref trick that makes things a bit magic :
my $hash_ref = {}; # reference to a blank hash
my %h = ( foo => "1", bar => "2" );
push #{$hash_ref->{'1st_level'}->{'level-2'}->{'level_3'}->{'arr'}}, 123;
push #{$hash_ref->{'1st_level'}->{'level-2'}->{'level_3'}->{'arr'}}, 456;
$hash_ref->{'1st_level'}->{'level-2'}->{'level_3'}->{'arr'}->[2] = \%h;
use Data::Dumper;
print Dumper $hash_ref;
The output :
$VAR1 = {
'1st_level' => {
'level-2' => {
'level_3' => {
'arr' => [
123,
456,
{
'bar' => '2',
'foo' => '1'
}
]
}
}
}
};