I have recursively put together an array of hashes for perl, which looks something like this :
[
{
'Default' => {
'Elect' => { 'P' => 1 }
}
},
{
'Default' => {
'Elect' => { 'A' => 1 }
}
},
{
'Default' => {
'Elect' => { 'M' => 1 }
}
},
{
'Default' => {
'Elect' => { 'I' => 1 }
}
},
{
'Default' => {
'Picker' => { 'L' => 1 }
}
},
]
My aim is to make this more condensed and look like a single hash, as compared to array of hashes. Is there anyway in which i can make this array of hashes look like a hash:
{
'Default' =>{
'Elect' =>{
'P' => 1,
'A' => 1,
'M' => 1,
'I' => 1,
},
'Picker' => {
'L' => 1
}
}
}
Well, here is a simple recursive procedure to merge two hash references:
sub merge {
my ($xs, $ys) = #_;
while (my ($k, $v) = each %$ys) {
if ('HASH' eq ref $v) {
merge($xs->{$k} //= {}, $v);
}
else {
$xs->{$k} = $v;
}
}
}
Then:
my $data = ...; # your input data structure
my $acc = {};
merge($acc, $_) for #$data;
which produces the result you desire in $acc.
There is also the Hash::Merge module, with that:
use Hash::Merge 'merge';
my $acc = {};
$acc = merge $acc, $_ for #$data;
Related
I have a set of nested hashes. I would like to add the string "Assembly" to the array value associated with [:dennis_ritche][:languages]
def adding_to_dennis
programmer_hash =
{
:grace_hopper => {
:known_for => "COBOL",
:languages => ["COBOL", "FORTRAN"]
},
:alan_kay => {
:known_for => "Object Orientation",
:languages => ["Smalltalk", "LISP"]
},
:dennis_ritchie => {
:known_for => "Unix",
:languages => ["C"]
}
}
programmer_hash[:dennis_ritchie][:languages] << "Assembly"
end
This is the error I get no implicit conversion of Symbol into Integer"
I think the problem you're seeing is you're manipulating the hash inside the method and as a result are inadvertently returning the wrong thing. This method returns an Array because that's the last operation performed (<< on Array return the modified Array).
To fix it define a method that does the manipulation:
def add_to_hash(hash, programmer = :dennis_ritchie, language = 'Assembly')
hash[programmer][:languages] << language
end
Make that independent of the definition:
programmer_hash =
{
:grace_hopper => {
:known_for => "COBOL",
:languages => ["COBOL", "FORTRAN"]
},
:alan_kay => {
:known_for => "Object Orientation",
:languages => ["Smalltalk", "LISP"]
},
:margaret_hamilton => {
:known_for => "Apollo Program",
:languages => ["Assembly"]
},
:dennis_ritchie => {
:known_for => "Unix",
:languages => ["C"]
}
}
Then call it to manipulate the hash:
add_to_hash(programmer_hash)
The programmer_hash structure is then updated.
I'm trying to generate array from hash reference, created by joining all keys of hashes with sorting.
Consider I have dynamic hash reference like
my $hash_ref = {
'A1' => {
'B2' => {
'C1' => {
'D1' => {},
'D2' => {},
'D3' => {}
}
},
'B3' => {
'C1' => {
'D2' => {},
'D1' => {},
'D3' => {}
}
},
'B1' => {
'C1' => {
'D1' => {},
'D2' => {}
}
}
}
};
how to create array from above hash like
#arr = qw/A1B1C1D1 A1B1C1D2 A1B2C1D1 ..../;
below is the code I tried(which is not working)
my $out = hash_walk($hash_ref);
say Dumper $out;
sub hash_walk {
my $hash = shift;
my $array_ref;
my $temp_arr;
my #temp_arr2;
foreach my $k ( sort keys %$hash ) {
$v = $$hash{$k};
if ( ref($v) eq 'HASH' ) {
# Recurse.
$temp_arr = hash_walk( $v);
}
push #$array_ref, $k if $k;
my (#lvlfirst, #lvlnext );
if ($array_ref && $temp_arr){
#lvlfirst = #$array_ref;
#lvlnext = #$temp_arr;
}
for ( my $i = 0 ; $i <= $#lvlfirst ; $i++ ) {
for ( my $j = 0 ; $j <= $#lvlnext ; $j++ ) {
push #temp_arr2, "$lvlfirst[$i]$lvlnext[$j]"; ##Trying to join here
}
}
}
return \#temp_arr2;
}
XML is:
<root>
<class1 name="A1">
<class2 name="B1">
<class3 name="C1">
<class4 name="D1"></class4>
<class4 name="D2"></class4>
</class3>
</class2>
<class2 name="B2">
<class3 name="C1">
<class4 name="D1"></class4>
</class3>
</class2>
<class2 name="B3">
<class3 name="C1">
<class4 name="D1"></class4>
<class4 name="D2"></class4>
<class4 name="D3"></class4>
</class3>
</class2>
</class1>
</root>
You should really make some effort yourself before coming to SO for help. We're far more likely to help you fix broken code than just give you an answer.
But I'm feeling generous and I have a couple of minutes to spare.
The brute force approach would be to walk through every key at every level in the hash.
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use Data::Dumper;
my $hash_ref = {
'A1' => {
'B2' => {
'C1' => {
'D1' => {},
'D2' => {},
'D3' => {}
}
},
'B3' => {
'C1' => {
'D2' => {},
'D1' => {},
'D3' => {}
}
},
'B1' => {
'C1' => {
'D1' => {},
'D2' => {}
}
}
}
};
my #arr;
for my $l1 (sort keys %$hash_ref) {
for my $l2 (sort keys %{$hash_ref->{$l1}}) {
for my $l3 (sort keys %{$hash_ref->{$l1}{$l2}}) {
for my $l4 (sort keys %{$hash_ref->{$l1}{$l2}{$l3}}) {
push #arr, "$l1$l2$l3$l4";
}
}
}
}
say Dumper \#arr;
This produces the output:
$VAR1 = [
'A1B1C1D1',
'A1B1C1D2',
'A1B2C1D1',
'A1B2C1D2',
'A1B2C1D3',
'A1B3C1D1',
'A1B3C1D2',
'A1B3C1D3'
];
Update: Here's a recursive solution:
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use Data::Dumper;
my $hash_ref = {
'A1' => {
'B2' => {
'C1' => {
'D1' => {},
'D2' => {},
'D3' => {}
}
},
'B3' => {
'C1' => {
'D2' => {},
'D1' => {},
'D3' => {}
}
},
'B1' => {
'C1' => {
'D1' => {},
'D2' => {}
}
}
}
};
my #arr = walk_hash($hash_ref, '');
say Dumper \#arr;
sub walk_hash {
my ($hash_ref, $prefix) = #_;
return $prefix unless keys %$hash_ref;
return map { walk_hash($hash_ref->{$_}, "$prefix$_") } sort keys %$hash_ref;
}
I would tackle this differently - as this is XML, I would skip the intermediate 'mangle the XML into a hash' step, and just work with it directly.
Something like this does what you want:
#!/usr/bin/env perl
use strict;
use warnings 'all';
use XML::Twig;
use Data::Dumper;
my $twig = XML::Twig -> new -> parsefile ('your.xml');
my #node_keys;
#find all the nodes with a name attribute.
#then grep out the ones that have child nodes.
foreach my $elt ( grep { not $_ -> descendants } $twig -> get_xpath('//*[#name]') ){
my $path = $elt -> att('name');
my $cursor = $elt;
#recurse upwards through 'parent' nodes with a 'name' attribute.
while ( $cursor -> parent -> att('name') ) {
$path = $cursor -> parent -> att('name') . $path;
$cursor = $cursor -> parent;
}
push #node_keys, $path;
}
print Dumper \#node_keys;
Gives output:
$VAR1 = [
'A1B1C1D1',
'A1B1C1D2',
'A1B2C1D1',
'A1B3C1D1',
'A1B3C1D2',
'A1B3C1D3'
];
Note - because it's walking in 'XML order' it's preserving the same ordering as source. That might be called a feature, or you can sort it afterwards.
But I would question perhaps, what you're trying to accomplish by making these compounds of 'name' attributes - it may be that you can solve the task more effectively through XML parsing and xpath queries.
I have Json logs in an array as follows:
e":[{"n":"3/0/1","st":"CONTENT","v":"Sensortag"},
{"n":"3/0/3","st":"CONTENT","v":"Contiki-develop-20150508-409-g2147b9e"},
{"n":"3/0/13","st":"CONTENT","v":"1970-01-09T21:02:18Z"},
{"n":"3301/0/5700","st":"CONTENT","v":"376.64"},
{"n":"3303/0/5700","st":"CONTENT","v":"22.843"},
{"n":"3304/0/5700","st":"CONTENT","v":"63.53"},
{"n":"3315/0/5700","st":"CONTENT","v":"1000.34"}]
I would like to delete the first 3 elements from the array and keep the 4 last ones using a filter.
I have this as my filter:
filter {
if ([type] == "testbed"){
if [MessageParserJson][e[{}] in [MessageParserJson]{
mutate {
remove_field => ["[MessageparserJson][e[{0}]]" , "[MessageparserJson][e[{1}]]" , "[MessageParserJson][e[{2}]]"]
add_field => { "[MessageParserJson][e[{3}]]" => "MessageParser" }
add_field => { "[MessageParserJson][e[{4}]]" => "MessageParser" }
add_field => { "[MessageParserJson][e[{5}]]" => "MessageParser" }
add_field => { "[MessageParserJson][e[{6}]]" => "MessageParser" }
}
}
drop {
remove_field => ["MessageParserJson"]
}
}
}
But Logstash puts itself in error
you could use the Ruby filter for that, to e.g. remove the first three elements this should work:
filter {
ruby {
code => "event['MessageParserJson'].slice!(0,3)"
}
}
Cheers
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'
}
]
}
}
}
};