Convert a flat datastructure into a tree - perl

I have an array of hashes. Each element in the array is a node in a hierarchical tree and has referential data for who the parent is. I will have thousands and hundreds of thousands of nodes in the tree... essentially an unknown set of nodes has to be converted to JSON (shown below) for use with http://bl.ocks.org/robschmuecker/7880033
UPDATE: position_id is a node in the heretical tree. placement_id is the parent's position_id (adjacency referential tree).
UPDATE: Here's the full AoH Data::Dumper result with Nested Set and Adjacency result from a modified version of DBIx::Tree::NestedSet (custom).
$VAR1 = [
{
'lft' => '673',
'id' => '109',
'date_created' => '2015-08-15',
'level' => '7',
'user_id' => '13',
'placement_id' => '11',
'position_id' => '13',
'status' => '1',
'structure_id' => '1',
'rght' => '684'
},
{
'placement_id' => '13',
'position_id' => '22',
'status' => '1',
'structure_id' => '1',
'rght' => '679',
'lft' => '674',
'date_created' => '2015-08-15',
'id' => '116',
'level' => '8',
'user_id' => '22'
},
{
'user_id' => '101',
'level' => '9',
'id' => '200',
'date_created' => '2015-08-15',
'lft' => '675',
'rght' => '676',
'structure_id' => '1',
'status' => '1',
'position_id' => '101',
'placement_id' => '22'
},
{
'date_created' => '2015-08-15',
'id' => '201',
'level' => '9',
'user_id' => '374',
'lft' => '677',
'structure_id' => '1',
'rght' => '678',
'placement_id' => '22',
'position_id' => '374',
'status' => '1'
},
{
'lft' => '680',
'user_id' => '95',
'level' => '8',
'id' => '117',
'date_created' => '2015-08-15',
'status' => '1',
'position_id' => '95',
'placement_id' => '13',
'rght' => '681',
'structure_id' => '1'
}
];
THIS IS THE GOAL, For this example I need to end up with:
{
"name": "13",
"children": [
{
"name": "22",
"children": [
{
"name": "101"
},
{
"name": "374"
}
]
},
{
"name": "95"
}
]
}
You can also see the format I am trying to arrive at here (minus size):
http://bl.ocks.org/robschmuecker/7880033#flare.json
My failed approach(es) included various attempts at looping through the array of hashes to create a recursive Hash of Hashes that can then be used with the JSON Perl module to create the actual JSON I need.

my $data = [
{ position_id => 123, placement_id => undef },
{ position_id => 456, placement_id => 123 },
{ position_id => 789, placement_id => 123 },
# ...
];
my $roots;
{
my %recs_by_name;
my %children_by_parent_name;
for my $row (#$data) {
my $name = $row->{position_id};
my $parent_name = $row->{placement_id};
my $rec = {
name => $name,
};
push #{ $children_by_parent_name{$parent_name // 'root'} }, $rec;
$recs_by_name{$name} = $rec;
}
$roots = delete($children_by_parent_name{root}) || [];
for my $name (keys(%children_by_parent_name)) {
my $children = $children_by_parent_name{$name};
if ( my $rec = $recs_by_name{$name} ) {
$rec->{children} = $children;
} else {
die("Parent $name doesn't exist.\n");
push #$roots, #$children;
}
}
}
print(Dumper($roots));
Tested.
You appear to have the depth of each node available to you (level). Simpler code could be used if your data was sorted by increasing depths.

While it was #ikegami who ultimately answered the question that led to the solution. I believe the following adaptation adds 4 important elements/clarifications I found helpful, and thought others reading this question and answer would also find useful.
1- Clear addition of all key,value pairs from the originating AoH to the resulting HOH. See while loop.
2- A Child node counter.
3- Inclusion and use of the encode_json function from JSON
4- The result is also an Array with a Hash as the first element. Newbies (like me) might find the explicit #{$roots}[0] passed to encode_json as helpful.
At first I had a similar adapted solution posted as an UPDATE within my question, but was admonished that it was bad etiquette and instructed to post an answer.
#ikegami's deserves the credit for the core of the solution.
sub get_jsonTree {
my ($array_of_hashes_ref) = #_;
my $roots;
my %recs_by_name;
my %children_by_parent_name;
my %count;
for my $row (#$array_of_hashes_ref) {
my $name = $row->{position_id};
my $parent_name = $row->{placement_id};
my $rec = {
name => $name,
};
## Added to loop through all key,value pairs and add them to $rec
while ( my ($key, $value) = each(%$row) ) {
$rec->{$key} = $value;
}
##Added To Count Child Nodes
$count{$parent_name} = 0 if (!$count{$parent_name});
$rec->{'child_count'} = $count{$parent_name};
$count{$parent_name}++;
push #{ $children_by_parent_name{$parent_name // 'root'} }, $rec;
$recs_by_name{$name} = $rec;
}
$roots = delete($children_by_parent_name{root}) || [];
for my $name (keys(%children_by_parent_name)) {
my $children = $children_by_parent_name{$name};
if ( my $rec = $recs_by_name{$name} ) {
$rec->{children} = $children;
} else {
$util{'test'} .= "Parent $name doesn't exist.\n<BR>";
push #$roots, #$children;
}
}
use JSON;
my $json_str = encode_json(#{$roots}[0]);
return $json_str;
}
my $array_of_hashes_ref = [
{ position_id => 123, placement_id => undef },
{ position_id => 456, placement_id => 123 },
{ position_id => 789, placement_id => 123 },
# ...
];
my $json_str = &get_jsonTree($array_of_hashes_ref);

Related

Looping through hashes in Perl gives odd result - example script provided

I'm revisiting a perl application that I built several years ago. I have to rebuild some of it. But today I'm stuck. I'm having some trouble with hashes. I have this test script that loops through some hashes. What I don't understand is that the second time the last loop gives 'pid1' => $VAR1->[1]{'deal'}{'pid1'} as output. I'm expecting a hash with product data. What am I doing wrong?
#!usr/bin/perl
use strict;
use warnings;
use Data::Dumper qw(Dumper);
my %stores;
push #{$stores{'store1'}}, 'pid1';
push #{$stores{'store1'}}, 'pid2';
push #{$stores{'store2'}}, 'pid1';
print Dumper(\%stores);
my %products = (
'pid1' => {
'name' => 'Product 1',
'color' => 'red'
},
'pid2' => {
'name' => 'Product 2',
'color' => 'blue'
}
);
print Dumper \%products;
my #offers;
foreach my $storeid (keys %stores) {
foreach my $pid (#{$stores{$storeid}}) {
my %offer;
$offer{$storeid}{'deal'}{$pid} = $products{$pid};
push(#offers, %offer);
}
}
print Dumper(\#offers);
$VAR1 = {
'store1' => [
'pid1',
'pid2'
],
'store2' => [
'pid1'
]
};
$VAR1 = {
'pid2' => {
'name' => 'Product 2',
'color' => 'blue'
},
'pid1' => {
'color' => 'red',
'name' => 'Product 1'
}
};
$VAR1 = [
'store1',
{
'deal' => {
'pid1' => {
'color' => 'red',
'name' => 'Product 1'
}
}
},
'store1',
{
'deal' => {
'pid2' => {
'name' => 'Product 2',
'color' => 'blue'
}
}
},
'store2',
{
'deal' => {
'pid1' => $VAR1->[1]{'deal'}{'pid1'}
}
}
];
It means
$VAR1->[1]{'deal'}{'pid1'} # $offers[1]{'deal'}{'pid1'}
and
$VAR1->[5]{'deal'}{'pid1'} # $offers[5]{'deal'}{'pid1'}
are both references to the same hash, which looks like
{
'color' => 'red',
'name' => 'Product 1'
}
Maybe it's clearer if you use local $Data::Dumper::Purity = 1; to produce code that can actually be executed.
$VAR1 = [
'store1',
{
'deal' => {
'pid1' => {
'color' => 'red',
'name' => 'Product 1'
}
}
},
'store1',
{
'deal' => {
'pid2' => {
'name' => 'Product 2',
'color' => 'blue'
}
}
},
'store2',
{
'deal' => {
'pid1' => {}
}
}
];
$VAR1->[5]{'deal'}{'pid1'} = $VAR1->[1]{'deal'}{'pid1'};

Accessing Specific OTRS Dynamic Field value via SOAP

How do I further access this dynamic field value? Upon using below dumper,
print Dumper( $Body->{$ResponseKey} );
The result is :
$VAR1 = {
'Ticket' => {
'Title' => 'TPLUS Service PIC',
'DynamicField' => [
{
'Value' => '43312',
'Name' => 'BugID'
},
{
'Value' => '6',
'Name' => 'OTRSMV'
},
{
'Value' => '6.13',
'Name' => 'OTRSPLV'
},
{
'Value' => 'Dev',
'Name' => 'OTRSUse'
},
{
'Value' => '2018-03-02 00:28:00',
'Name' => 'RefDate'
},
{
'Value' => '0',
'Name' => 'RefNumber'
},
{
'Value' => '',
'Name' => 'StartTime'
}
],
'StateType' => 'open',
'SLAID' => ''
}
};
How can I access the single value of DynamicField->RefDate ? Thanks
my $fields = $Body->{$ResponseKey}{Ticket}{DynamicField};
my ($ref_date) =
map $_->{Value},
grep $_->{Name} eq 'RefDate',
#$fields;
or
my %fields;
$fields{ $_->{Name} } = $fields{ $_->{Value} }
for #{ $Body->{$ResponseKey}{Ticket}{DynamicField} };
my $ref_date = $fields{RefDate};

Accessing xml data using perl XML::Simple

I have some xml data, the dump looks like this:
$VAR1 = {
'Members' => [
{
'Age' => '19',
'Name' => 'Bob'
},
{
'Age' => '18',
'Name' => 'Jane'
},
{
'Age' => '21',
'Name' => 'Pat'
},
{
'Age' => '22',
'Name' => 'June'
}
],
'Sports' => [
{
'Players' => '20',
'Name' => 'Tennis'
},
{
'Players' => '35',
'Name' => 'Basketball'
}
],
};
I have tried the following code to print out the data:
foreach my $member (#($xml->{Members})) {
print("Age: $xml->{Age}");
}
But keep getting errors like:
Can't use string ("4") as a HASH ref while "strict refs" in use
Any idea why this won't work?
You are using the wrong syntax.
# here ... and here
# V V
foreach my $member (#($xml->{Members})) { ... }
To dereference, you need curly braces {}, not parenthesis ().
Once you've fixed that (which I think was a typo in the question, not in your real code), you have:
foreach my $member ( #{ $xml->{Members} } ) {
print "Age: $xml->{Age}";
}
But that's still wrong. You want to access the $member, not the whole $xml structure, because that doesn't have an Age, does it?
foreach my $member ( #{ $xml->{Members} } ) {
print "Age: $member->{Age}\n";
}
That will give you
Age: 19
Age: 18
Age: 21
Age: 22

perl - How to loop access hashref?

I have some data in hashref format. I fetch data from graph.facebook.com
How to loop access?
$var = \{
'data' => [
{
'id' => '312351465029_10154168935475030',
'name' => 'Timeline Photos 1'
},
{
'name' => 'Bangchak\'s cover photo',
'id' => '312351465029_10154168087455030',
},
{
'id' => '312351465029_10154168081875030',
'name' => 'Timeline Photos 2',
}
],
'paging' => {
'previous' => 'https://graph.facebook.com/v2.6/312351465029/2',
'next' => 'https://graph.facebook.com/v2.6/312351465029/3'
}
};
These code Didn't work.
foreach $m ($var->{data})
{
if ( $m->{name} =~ /Timeline/i )
{
print "id = $m->{id}\n";
}
}
You need to dereference the array (perldoc perldsc):
use warnings;
use strict;
my $var = {
'data' => [
{
'id' => '312351465029_10154168935475030',
'name' => 'Timeline Photos 1'
},
{
'name' => 'Bangchak\'s cover photo',
'id' => '312351465029_10154168087455030',
},
{
'id' => '312351465029_10154168081875030',
'name' => 'Timeline Photos 2',
}
],
'paging' => {
'previous' => 'https://graph.facebook.com/v2.6/312351465029/2',
'next' => 'https://graph.facebook.com/v2.6/312351465029/3'
}
};
foreach my $m (#{ $var->{data} }) {
if ( $m->{name} =~ /Timeline/i )
{
print "id = $m->{id}\n";
}
}
__END__
id = 312351465029_10154168935475030
id = 312351465029_10154168081875030

Extracting specific elements from hash

my %hash = {
'student1' => {
'Name' => 'aaa',
'Age' => '20',
'Subjects' => ['Maths','Science']
},
'student2' => {
'Name' => 'bbb',
'Age' => '22',
'Subjects' => ['English','Science']
}
}
my $hashRef = \%hash;
how do i extract the second subject name from this using hashref ?
Your declaration of %hash is incorrect, do this instead:
my %hash = (
'student1' => {
'Name' => 'aaa',
'Age' => '20',
'Subjects' => ['Maths','Science']
},
'student2' => {
'Name' => 'bbb',
'Age' => '22',
'Subjects' => ['English','Science']
}
);
Note the parens instead og brace.
Then to get the second subject :
say $hashRef->{student1}{Subjects}[1];
Your code is wrong, { } creates a hashref and you are storing it in a hash. You should do:
my %hash = (
'student1' => {
'Name' => 'aaa',
'Age' => '20',
'Subjects' => ['Maths','Science']
},
'student2' => {
'Name' => 'bbb',
'Age' => '22',
'Subjects' => ['English','Science']
}
);
my $hashRef = \%hash;
or even better:
my $hashref = {
student1 => { ... },
student2 => { ... },
};
Then you can access with:
$hashRef->{student2}->{Subjects}[1]
Subjects are an array reference inside a hash inside a hash.
$hashRef->{student1}{Subjects}[1]
Also, do not use curly brackets to initilize a hash, they create an anonymous hash. Use round parentheses:
my %hash = ( ... );