I got an array of paths
C:\A
C:\B\C
D:\AB
and I'd like to have these in a hash array tree so I can go through them in a TT2 template.
What I mean is like this:
#dirs = [
{
name => "C:",
subs => [
{
name => "A",
subs => [],
},
{
name => "B",
subs => [
{
name => "C",
subs => [],
}
],
}
]
},
{
name => "D:",
subs => [
{
name => "AB",
subs => [],
}
],
}
]
I also know that I'm probably doing brainderp here so I'm open to other approaches, only requirement is turning that list of paths into something you can rebuild as a tree with the TT2 Template Toolkit.
Also what's that structure called? I just thought of hash array tree but I bet that's wrong.
Here's a very short approach. Note that this can only be so simple because I changed your data format to a hash of hashes which perfectly matches your tree structure. See the code below to transform the resulting structure to yours.
my $tree = {root => {}};
foreach my $input (<DATA>) { chomp $input;
my $t = $tree;
$t = $t->{$_} //= {} for split /\\/ => $input;
}
use Data::Dumper; print Dumper $tree;
__DATA__
C:\A
C:\B\C
D:\AB
C:\B\A
C:\B\A\C
Output:
$VAR1 = {
'C:' => {
'A' => {},
'B' => {
'A' => {
'C' => {}
},
'C' => {}
}
},
'D:' => {
'AB' => {}
}
};
To transform this data structure into yours, simply use this code:
sub transform {
my $tree = shift;
my #children = ();
while (my ($name, $children) = each %$tree) {
push #children, {
name => $name,
subs => [ transform($children) ],
}
}
return #children;
}
my $AoH_tree = {name => 'root', subs => [transform($tree)] };
Done. :) For a completely different approach with much more sugar, power and readability, but much more LOC, see my other answer.
This is a longer but much more readable and more comfortable solution. You don't have to (and probably don't want to) use this, but maybe it can help (not only you) to learn more about different approaches. It introduces a small Moo class for tree nodes which can add names recursively to itself with readable sorting and stringification methods.
Edit: for a completely different and extremely short alternative, see my other answer. I divided it up in two answers because they are completely different approaches and because this answer is already long enough. ;)
Tree class
Note this is basically no more than your nested AoHoAoH... structure - with a litte bit sugar added. ;)
# define a tree structure
package Tree;
use Moo; # activates strict && warnings
use List::Util 'first';
# name of this node
has name => (is => 'ro');
# array ref of children
has subs => (is => 'rw', isa => sub { die unless ref shift eq 'ARRAY' });
Now after the basic preparations (our objects have one scalar name and one array ref subs) we come to the main part of this answer: the recursive add_deeply method. Note that from here everything reflects the recursive nature of your data structure:
# recursively add to this tree
sub add_deeply {
my ($self, #names) = #_;
my $next_name = shift #names;
# names empty: do nothing
return unless defined $next_name;
# find or create a matching tree
my $subtree = first {$_->name eq $next_name} #{$self->subs};
push #{$self->subs}, $subtree = Tree->new(name => $next_name, subs => [])
unless defined $subtree;
# recurse
$subtree->add_deeply(#names);
}
The following two methods are not that important. Basically they are here to make the output pretty:
# sort using node names
sub sort {
my $self = shift;
$_->sort for #{$self->subs}; # sort my children
$self->subs([ sort {$a->name cmp $b->name} #{$self->subs} ]); # sort me
}
# stringification
use overload '""' => \&to_string;
sub to_string {
my $self = shift;
my $prefix = shift // '';
# prepare
my $str = $prefix . '{TREE name: "' . $self->name . '"';
# stringify children
if (#{$self->subs}) {
$str .= ", children: [\n";
$str .= $_->to_string(" $prefix") for #{$self->subs};
$str .= "$prefix]";
}
# done
return $str . "}\n";
}
How to use this
Now comes the simple part. Just read the input (from __DATA__ here) and add_deeply:
# done with the tree structure: now use it
package main;
# parse and add names to a tree
my $tree = Tree->new(name => 'root', subs => []);
foreach my $line (<DATA>) {
chomp $line;
$tree->add_deeply(split /\\/ => $line);
}
# output
$tree->sort;
print $tree;
__DATA__
C:\A
C:\B\C
D:\AB
C:\B\A
C:\B\A\C
Output:
{TREE name: "root", children: [
{TREE name: "C:", children: [
{TREE name: "A"}
{TREE name: "B", children: [
{TREE name: "A", children: [
{TREE name: "C"}
]}
{TREE name: "C"}
]}
]}
{TREE name: "D:", children: [
{TREE name: "AB"}
]}
]}
I did one with a complex hash structure keeping track of already placed nodes, and then I did this one. More steps, but somewhat leaner code.
while ( <> ) {
chomp;
my $ref = \#dirs;
foreach my $dir ( split /\\/ ) {
my $i = 0;
$i++ while ( $ref->[$i] and $ref->[$i]{name} ne $dir );
my $r = $ref->[$i] ||= { name => $dir, subs => [] };
$ref = $r->{subs};
}
}
Related
I have an array of states in the format
('AL','Alabama','AK','Alaska','AR','Arkansas'...)
which I want formatted like:
[{'AL' => 'Alabama'},...]
This is primarily so that I can more easily loop through using the HTML::Template module (https://metacpan.org/pod/HTML::Template#TMPL_LOOP)
I'm fairly new to perl, so unsure about how to do this sort of action and can't find something similar enough.
Wouldn't the following make more sense for HTML::Template?
states => [ { id => 'AL', name => 'Alabama' }, ... ]
This would allow you to use the following template:
<TMPL_LOOP NAME=states>
<TMPL_VAR NAME=name> (<TMPL_VAR NAME=id>)
</TMPL_LOOP>
To achieve that, you can use the following:
use List::Util 1.29 qw( pairmap );
states => [ pairmap { +{ id => $a, name => $b } } #states ]
That said, you're probably generating HTML.
<select name="state">
<TMPL_LOOP NAME=states>
<option value="<TMPL_VAR NAME=id_html>"><TMPL_VAR NAME=name_html></option>
</TMPL_LOOP>
</select>
To achieve that, you can use the following:
use List::Util 1.29 qw( pairmap );
{
my %escapes = (
'&' => '&',
'<' => '<',
'>' => '>',
'"' => '"',
"'" => ''',
);
sub text_to_html(_) { $_[0] =~ s/([&<>"'])/$escapes{$1}/rg }
}
states => [ pairmap { +{ id_html => $a, name_html => $b } } map text_to_html, #states ]
use List::Util 1.29;
#state_hashes = List::Util::pairmap { +{ $a => $b } } #states;
Unless you need to keep this hash around for later use I think that simply looping through the elements two at a time would be simpler. You can accomplish this type of looping easily with splice:
my #states = ('AL','Alabama','AK','Alaska','AR','Arkansas'...);
while (my ($code, $name) = splice(#states, 0, 2)) {
# operations here
}
Alternatively, you can use this same approach to create the data structure you want:
my #states = ('AL','Alabama','AK','Alaska','AR','Arkansas'...);
my #state_hashes = ();
while (my ($code, $name) = splice(#states, 0, 2)) {
push #state_hashes, { $code => $name };
}
# do w/e you want with #state_hashes
Note: splice will remove elements from #states
bundle_by from List::UtilsBy can easily create this format:
use strict;
use warnings;
use List::UtilsBy 'bundle_by';
my #states = ('AL', 'Alabama', 'AK', 'Alaska', 'AR', 'Arkansas', ... );
my #hashes = bundle_by { +{#_} } 2, #states;
map solution with a few perlish things
my #states = ('AL','Alabama','AK','Alaska','AR','Arkansas','VT','Vermont');
my %states;
map { $states{$states[$_]} = $states[$_+1] unless $_%2 } 0..$#states;
I have a string as input, say apple.mango.orange = 100
I also have a hash reference:
$inst = {
'banana' => 2,
'guava' => 3,
'apple' => {
'mango' => {
'orange' => 80
}
}
};
I want to modify the value of orange using the input string. Can someone please help me how I could do this?
I tried splitting the string into (key, value) pair. I then did the following on the key string:
my $key2 = "\$inst->{".$key."}";
$key2 =~ s/\./}->{/g;
$$key2 = $value;
This does not work as intended. Can someone help me out here? I have read the Perl FAQ about not using a variable value as variable but I am unable to think of an alternative.
You are building string that consists of (buggy) Perl code, but you never ask Perl to execute it. ...but that's not the right approach.
sub dive_val :lvalue {
my $p = \shift;
$p = \($$p->{$_}) for #_;
$$p
}
my #key = split /\./, "apple.mango.orange";
dive_val($inst, #key) = $value;
or
use Data::Diver qw( DiveVal );
my #key = split /\./, "apple.mango.orange";
DiveVal($inst, map \$_, #key) = $value;
Not only is a symbolic reference a very bad idea here, it doesn't even solve your problem. You're building an expression in $key2, and just jamming another dollar sign in front of its name won't make perl execute that code. For that you would need eval, which is another bad idea
You can install and use the Data::Diver module, which does exactly this sort of thing, or you can simply loop over the list of hash keys, picking up a new hash reference each time and assigning the value to the element with the last key
The biggest issue is actually parsing the incoming string into a list of keys and a value. This code implements a subroutine apply which applies the implied operation in the string to a nested hash. Unless you are confident of your data, it needs some error checking addingto make sure each of the keys in the list exists. The Data:;Dumper output is just to demonstrate the validity of the result
use strict;
use warnings 'all';
use Data::Dumper;
my $inst = { 'banana' => 2, 'guava' => 3, 'apple' => { 'mango' => { 'orange' => 80 } } };
my $s = 'apple.mango.orange = 100';
apply($s, $inst);
print Dumper $inst;
sub apply {
my ($operation, $data) = #_;
my ($keys, $val) = $operation =~ /([\w.]+)\s*=\s*(\d+)/;
my #keys = split /\./, $keys;
my $last = pop #keys;
my $hash = $data;
$hash = $hash->{$_} for #keys;
$hash->{$last} = $val;
}
output
$VAR1 = {
'banana' => 2,
'apple' => {
'mango' => {
'orange' => '100'
}
},
'guava' => 3
};
I have created this simple subroutine.
use List::Util qw(pairmap);
sub pairGroupBy(&#) {
my($irCode, #iaItems) = #_;
my %laResult = ();
pairmap {
my $lsKey = $irCode->();
if (!defined($lsKey)) {
die "Trying to pairGroup by nonexisting key '$lsKey'";
}
push #{$laResult{$lsKey}}, $a => $b;
} #iaItems;
return %laResult;
}
It works well until the subroutine is used from the same file where it is defined. When I move it to some package then variables $a and $b becomes undefined inside the $irCode->() callback.
I have learned from the List::Util source code that this code do the trick:
my $caller = caller;
local(*{$caller."::a"}) = \my $a;
local(*{$caller."::b"}) = \my $b;
So I'have modified my subroutine in this way:
use List::Util qw(pairmap);
sub pairGroupBy(&#) {
my($irCode, #iaItems) = #_;
my $caller = caller;
my %laResult = ();
pairmap {
no strict 'refs';
local(*{$caller."::a"}) = \$a; # <---- the line 96
local(*{$caller."::b"}) = \$b;
my $lsKey = $irCode->();
if (!defined($lsKey)) {
die "Trying to pairGroup by nonexisting key '$lsKey'";
}
push #{$laResult{$lsKey}}, $a => $b;
} #iaItems;
return %laResult;
}
But I need to use the no strict 'refs'; line (the List::Util source code does not use it). Otherwise the error message appears:
Can't use string ("main::a") as a symbol ref while "strict refs" in use at /home/.../bin/SatFunc.pm line 96.
My question is: Is there some better way how to define $a and $b variables in the caller's context without using no strict 'refs';?
I want my function will be used in the same way as pairmap, pairgrep etc.
EDIT:
#simbabque asked for an example, how the function is used. So this is an example:
my %laHoH = (
aa => {
color => 'yellow',
item => 'sun',
active => 1
},
bb => {
color => 'blue',
item => 'sky',
active => 1
},
cc => {
color => 'green',
item => 'grass',
active => 0
},
dd => {
color => 'blue',
item => 'watter',
active => 1
}
);
my %laGrouped = pairGroupBy {
$b->{color}
} pairgrep {
$b->{active}
} %laHoH;
The function then returns this structure:
{
'yellow' => [
'aa',
{
'color' => 'yellow',
'item' => 'sun',
'active' => 1
}
],
'blue' => [
'dd',
{
'active' => 1,
'item' => 'watter',
'color' => 'blue'
},
'bb',
{
'color' => 'blue',
'item' => 'sky',
'active' => 1
}
]
};
I'm not sure why you're seeing that problem, but I suspect you're overthinking matters. Using pairmap in void context like that seems a bad idea.
Can't you just convert your array into a hash and then iterate across that?
my %iaItemsHash = #iaItams;
while (my ($k, $v) = each %iaItemsHash) {
my $lsKey = $irCode->();
if (!defined($lsKey)) {
die "Trying to pairGroup by nonexisting key '$lsKey'";
}
push #{$laResult{$lsKey}}, $k => $v;
}
Update: In light of your comment, I've re-read your original question and spotted that you are talking about accessing the variables with the $irCode->() call.
The problem with my solution is that $k and $v are lexical variables and, therefore, aren't available outside of their lexical scope (this is generally seen as a feature!) The solution is to resort to good programming practice and to send the values into the subroutine as parameters.
Is there some better way how to define $a and $b variables in the caller's context without using no strict 'refs';?
You're asking us how to perform symbolic dereferences while asking Perl to prevent you from symbolic deferences. There's no reason to do that. If you want to perform symbolic dereferences, don't ask Perl to prevent you from doing it.
Even if Perl doesn't catch you doing it (i.e. if you manage to find a way to not trigger use strict qw( refs );), you'll still be using symbolic dereferences! You'd just be lying to yourself and to your readers.
Instead, it's best to document what you are doing. Use no strict qw( refs ); to signal that you are using doing something use strict qw( refs ); is suppose to block.
The following approach for building the same structure as your code is much less wasteful:
my %laGrouped;
for my $key (keys(%laHoH)) {
my $rec = $laHoH{$key};
next if !$rec->{active};
push #{ $laGrouped{ $rec->{color} } }, $key, $rec;
}
But let's improve the structure as well. The following approach produces a structure that's easier to use:
my %laGrouped;
for my $key (keys(%laHoH)) {
my $rec = $laHoH{$key};
next if !$rec->{active};
$laGrouped{ $rec->{color} }{$key} = $rec;
}
If you find yourself using pairGroupBy, you've probably went wrong somewhere. But here's a better implementation of it for educational purposes:
sub pairGroupBy(&#) {
my $cb = shift;
my $caller = caller;
my $ap = do { no strict 'refs'; \*{ $caller.'::a' } }; local *$ap;
my $bp = do { no strict 'refs'; \*{ $caller.'::b' } }; local *$bp;
my %groups;
while (#_) {
*$ap = \shift;
*$bp = \shift;
my $group = $cb->();
push #{ $groups{$group} }, $a, $b;
}
return %groups;
}
I have an LDAP directory that I'm querying using Net::LDAP. This gives me a set of parent-child relationships.
It's a directory of people - and includes a 'manager' DN (which is another field within the directory).
I'm having real trouble turning this manager->person set of records into a hierarchical structure.
What I've got so far is:
#!/usr/bin/env perl
use strict;
use warnings;
use Net::LDAP;
use Data::Dumper;
my %people;
my $ldap = Net::LDAP->new('my_ldap_server');
my $result = $ldap->bind('bind_dn');
die if $result->code;
my $search = $ldap->search(
base => 'ou=yaddayadda',
scope => 'subtree',
filter => 'objectClass=person',
attrs => ['manager'],
);
foreach my $found ( $search->entries ) {
my $mgr = $found->get_value('manager');
my $dn = $result->dn;
push( #{ $people{$mgr} }, $dn );
}
What this gives me is a hash of managers and the people who work for them (using DN, which is unique).
An entry from %people looks like:
$VAR1 = {
'cn=Firstname Lastname,ou=OrgUnit' => [
'cn=Personame Lastname,ou=OrgUnit',
'cn=AnotherPerson NameHere,ou=OrgUnit',
],
'cn=AnotherPerson NameHere,ou=OrgUnit' => [
'cn=Someone Else,ou=OrgUnit',
]
};
But I'm having trouble with turning that parent-child mapping into a hierarchical structure.
e.g.:
'ceo' => [
'pa' => [],
'head_of_dept' => [
'person' => [],
'person_with_staff' => [ 'person3', 'person4' ]
]
]
I'm at something of a loss for how to accomplish this. It seems it shouldn't be too hard to do, given that each person is unique within the organisation structure.
NB - in the above, I've got cn=AnotherPerson NameHere,ou=OrgUnit who has a subordinate, and I'm after making a nested mapping out of this:
e.g.:
$VAR1 = {
'cn=Firstname Lastname,ou=OrgUnit' => [
'cn=Personame Lastname,ou=OrgUnit',
'cn=AnotherPerson NameHere,ou=OrgUnit',
[
'cn=Someone Else,ou=OrgUnit'
]
]
};
What you need is a directed graph, and I suggest using the Graph::Directed module, whose methods are documented in Graph
This program will build the graph for you, but without any data I couldn't test it beyond making sure it compiles
use strict;
use warnings 'all';
use feature 'say';
use Net::LDAP;
use Graph::Directed;
use Data::Dumper;
my $ldap = Net::LDAP->new('my_ldap_server');
my $result = $ldap->bind('bind_dn');
die if $result->code;
my $search = $ldap->search(
base => 'ou=yaddayadda',
scope => 'subtree',
filter => 'objectClass=person',
attrs => ['manager'],
);
my $g = Graph::Directed->new;
for my $found ( $search->entries ) {
my $mgr = $found->get_value('manager');
my $dn = $result->dn;
$g->add_edge($mgr, $dn);
}
say $g;
The resulting Graph::Directed object has a stringification overload so you can examine it superficially by simply printing it, but when you want to interrogate the structure further you will need to know some of the terms of graph theory. For instance, $g->source_vertices will return a list of all nodes that have descendants but no parents—in this case, a list of senior management, or $g->is_cyclic will return true if your data has any loops anywhere
Here's an example of a program that uses your brief sample data to display a hierarchical tree of nodes
use strict;
use warnings 'all';
use Graph::Directed;
my $data = {
'cn=Firstname Lastname,ou=OrgUnit' => [
'cn=Personame Lastname,ou=OrgUnit',
'cn=AnotherPerson NameHere,ou=OrgUnit',
],
'cn=AnotherPerson NameHere,ou=OrgUnit' =>
[ 'cn=Someone Else,ou=OrgUnit', ]
};
my $g = Graph::Directed->new;
for my $mgr ( keys %$data ) {
$g->add_edge($mgr, $_) for #{ $data->{$mgr} };
}
dump_tree($_) for $g->source_vertices;
sub dump_tree {
my ($node, $level) = ( #_, 0);
print ' ' x $level, $node, "\n";
dump_tree($_, $level+1) for $g->successors($node);
}
output
cn=Firstname Lastname,ou=OrgUnit
cn=AnotherPerson NameHere,ou=OrgUnit
cn=Someone Else,ou=OrgUnit
cn=Personame Lastname,ou=OrgUnit
#Hunter McMillen unfortunately deleted his very good but slightly off answer. Here is my attempt to augment his code by turning the relationship from underling -> boss towards boss -> underlings.
To simulate the LDAP responses, I created a simple Moose class.
package Person;
use Moose;
has name => ( is => 'ro' );
has boss => ( is => 'ro', predicate => 'has_boss' );
package main;
use strict;
use warnings;
use Data::Printer;
# make a randomized list of people
my %people = map { $_->name => $_ }
map {
Person->new(
name => $_->[0], ( $_->[1] ? ( boss => $_->[1] ) : () )
)
} (
[qw( ceo )], [qw( head_of_dept ceo)],
[qw( person head_of_dept)], [qw( person_with_staff head_of_dept )],
[qw( person3 person_with_staff )], [qw( person4 person_with_staff )],
);
my %manages;
foreach my $p (values %people) {
push #{ $manages{ $p->boss } }, $p->name if $p->has_boss;
}
# this part shamelessly stolen from #HunterMcMillen's deleted answer
sub build_tree {
my ($person) = #_;
my #subtrees;
foreach my $managee ( #{ $manages{$person} } ) {
push #subtrees, build_tree($managee);
}
return { $person => \#subtrees };
}
p build_tree 'ceo';
Here's the output.
\ {
ceo [
[0] {
head_of_dept [
[0] {
person []
},
[1] {
person_with_staff [
[0] {
person4 []
},
[1] {
person3 []
}
]
}
]
}
]
}
This should be more or less what you want.
This is my problem:
I have a file-system like data-structure:
%fs = (
"home" => {
"test.file" => {
type => "file",
owner => 1000,
content => "Hello World!",
},
},
"etc" => {
"passwd" => {
type => "file",
owner => 0,
content => "testuser:testusershash",
},
"conf" => {
"test.file" => {
type => "file",
owner => 1000,
content => "Hello World!",
},
},
},
);
Now, to get the content of /etc/conf/test.file I need $fs{"etc"}{"conf"}{"test.file"}{"content"}, but my input is an array and looks like this: ("etc","conf","test.file").
So, because the length of the input is varied, I don't know how to access the values of the hash. Any ideas?
You can use a loop. In each step, you proceed one level deeper into the structure.
my #path = qw/etc conf test.file/;
my %result = %fs;
while (#path) {
%result = %{ $result{shift #path} };
}
print $result{content};
You can also use Data::Diver.
my #a = ("etc","conf","test.file");
my $h = \%fs;
while (my $v = shift #a) {
$h = $h->{$v};
}
print $h->{type};
Same logic as what others given, but uses foreach
#keys = qw(etc conf test.file content);
$r = \%fs ;
$r = $r->{$_} foreach (#keys);
print $r;
$pname = '/etc/conf/test.file';
#names = split '/', $pname;
$fh = \%fs;
for (#names) {
$fh = $fh->{"$_"} if $_;
}
print $fh->{'content'};
Path::Class accepts an array. It also gives you an object with helper methods and handles cross platform slash issues.
https://metacpan.org/module/Path::Class
You can just build the hash element expression and call eval. This is tidier if it is wrapped in a subroutine
my #path = qw/ etc conf test.file /;
print hash_at(\%fs, \#path)->{content}, "\n";
sub hash_at {
my ($hash, $path) = #_;
$path = sprintf q($hash->{'%s'}), join q('}{'), #$path;
return eval $path;
}