Anomalous push behaviour under Catalyst MVC - perl

I would expect the following code
my #array;
for my $rapport ( qw( value1 value2 value3 ) ) {
push #array, { key => $rapport };
}
to produce:
$VAR1 = [
{
'key' => 'value1'
},
{
'key' => 'value2'
},
{
'key' => 'value3'
}
];
However, running this code segment under Catalyst MVC I get:
$VAR1 = [
{
'key' => [ 'value', 'value2', 'value3' ]
},
];
Can someone please explain to me why?
EDIT: could anyone with the same issue please add an example? I cannot reproduce after some code changes, but as it has been upvoted 5 times I assume some other users have also experienced this issue?

This code example...
#!/usr/bin/perl
use Data::Dumper;
my #input = ( "var1", "var2", "var3" );
my #array;
for my $rapport ( #input ) {
push #array, { key => $rapport };
}
print Dumper( \#array );
exit;
produces ...
$VAR1 = [
{
'key' => 'var1'
},
{
'key' => 'var2'
},
{
'key' => 'var3'
}
];
But the following...
#!/usr/bin/perl
use Data::Dumper;
my #input = [ "var1", "var2", "var3" ]; # sometimes people forget to dereference their variables
my #array;
for my $rapport ( #input ) {
push #array, { key => $rapport };
}
print Dumper( \#array );
exit;
shows...
$VAR1 = [
{
'key' => [
'var1',
'var2',
'var3'
]
}
];
As you can see both examples loop through an array but the second one is an array, that was initialized with a reference value. Since in Catalyst you normally ship various values through your application via stash or similar constructs, you could check weather your array really contains scalar values : )

Related

Extract subset of XML with XML::Twig

I'm trying to use
XML::Twig
to extract a subset of an XML document so that I can convert it to CSV.
Here's a sample of my data
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Actions>
<Click>
<Field1>Data1</Field1>
<Field2>Data2</Field2>
</Click>
<Click>
<Field1>Data3</Field1>
<Field2>Data4</Field2>
</Click>
</Actions>
And here's an attempt at coding the desired outcome
#!/usr/bin/env perl
use strict;
use warnings;
use XML::Twig;
use Text::CSV; # later
use Data::Dumper;
my $file = shift #ARGV or die "Need a file to process: $!";
my $twig = XML::Twig->new();
$twig->parsefile($file);
my $root = $twig->root;
my #data;
for my $node ( $twig->findnodes( '//Click/*' ) ) {
my $key = $node->name;
my $val = $node->text;
push #data, { $key => $val }
}
print Dumper \#data;
which gives
$VAR1 = [
{
'Field1' => 'Data1'
},
{
'Field2' => 'Data2'
},
{
'Field1' => 'Data3'
},
{
'Field2' => 'Data4'
}
];
What I'm looking to create is an array of hashes, if that's best
my #AoH = (
{ Field1 => 'Data1', Field2 => 'Data2' },
{ Field1 => 'Data3', Field2 => 'Data4' },
)
I'm not sure how to loop through the data to extract this.
You structure has two levels, so you need two levels of loops.
my #data;
for my $click_node ( $twig->findnodes( '/Actions/Click' ) ) {
my %click_data;
for my $child_node ( $click_node->findnodes( '*' ) ) {
my $key = $child_node->name;
my $val = $child_node->text;
$click_data{$key} = $val;
}
push #data, \%click_data;
}
local $Data::Dumper::Sortkeys = 1;
print(Dumper(\#data));
Output:
$VAR1 = [
{
'Field1' => 'Data1',
'Field2' => 'Data2'
},
{
'Field1' => 'Data3',
'Field2' => 'Data4'
}
];

executing a function within an array within a hash in perl

I have a Perl data structurte like so
%myhash = (
k1 => v1,
kArray => [
{
name => "anonymous hash",
...
},
\&funcThatReturnsHash,
{
name => "another anonymous hash",
...
}
]
);
Elsewhere I iterate through the list in kArray which contains a bunch of hashes. I would like to either process the actual hash OR the hash returned by the function.
foreach my $elem( #{myhash{kArray}} ) {
if (ref($elem) == "CODE") {
%thisHash = &$elem;
}
else {
%thisHash = %$elem;
}
...
}
However ref ($elem) is always scalar or undefined. I tried func, &func, \&func, \%{&func}, in %myhash to no effect.
how do I extract the hash within the function in the main body?
Apart from the code sample you give being invalid Perl, the main problems seem to be that you are using == to compare strings instead of eq, and you are assigning a hash reference to a hash variable %thishash. I assure you that ref $elem never returns SCALAR with the data you show
It would help you enormously if you followed the common advice to use strict and use warnings at the top of your code
This will work for you
for my $elem ( #{ $myhash{kArray} } ) {
my $this_hash;
if ( ref $elem eq 'CODE' ) {
$this_hash = $elem->();
}
else {
$this_hash = $elem;
}
# Do stuff with $this_hash
}
or you could just use a map like this
use strict;
use warnings;
use 5.010;
use Data::Dump;
my %myhash = (
k1 => v1,
kArray => [
{
name => "anonymous hash",
},
\&funcThatReturnsHash,
{
name => "another anonymous hash",
}
]
);
for my $hash ( map { ref eq 'CODE' ? $_->() : $_ } #{ $myhash{kArray} } ) {
say $hash->{name};
}
sub funcThatReturnsHash {
{ name => 'a third anonymous hash' };
}
output
anonymous hash
a third anonymous hash
another anonymous hash
If you turn on strict and warnings, you'll see that:
foreach my $elem(#{mynahs{kArray}}) {
Isn't valid. You need at the very least a $ before mynahs.
But given something like this - your approach works - here's an example using map to 'run' the code references:
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
sub gimme_hash {
return { 'fish' => 'paste' };
}
my $stuff =
[ { 'anon1' => 'value' },
\&gimme_hash,
{ 'anon2' => 'anothervalue' }, ];
my $newstuff = [ map { ref $_ eq "CODE" ? $_->() : $_ } #$stuff ];
print Dumper $newstuff;
Turns that hash into:
$VAR1 = [
{
'anon1' => 'value'
},
{
'fish' => 'paste'
},
{
'anon2' => 'anothervalue'
}
];
But your approach does work:
foreach my $element ( #$stuff ) {
my %myhash;
if ( ref $element eq "CODE" ) {
%myhash = %{$element -> ()};
}
else {
%myhash = %$element;
}
print Dumper \%myhash;
}
Gives:
$VAR1 = {
'anon1' => 'value'
};
$VAR1 = {
'fish' => 'paste'
};
$VAR1 = {
'anon2' => 'anothervalue'
};

How can I find which keys in a Perl multi-level hash correspond to a given value?

I have a data structure which looks like this:
my %hoh = (
'T431567' => {
machin => '01',
bidule => '02',
truc => '03',
},
'T123456' => {
machin => '97',
bidule => '99',
truc => '69',
},
'T444444' => {
machin => '12',
bidule => '64',
truc => '78',
},
);
I want to search the various values of truc for a particular value and find the top-level attribute which corresponds to that entry. For example, looking for a value of 78, I want to find the result 'T444444', because $hoh{T444444}{truc} is 78.
How can I do this, please?
You can do this with grep:
my #keys = grep { $hoh{$_}{truc} == 78 } keys %hoh;
Note that this can return more than one key, if there are duplicate values in the hash. Also note that this is not particularly efficient, since it has to search the entire hash. In most cases it's probably fine, but if the hash can be very large and you may need to run lots of such queries against it, it may be more efficient to build a reverse index as suggested by Sobrique:
my %trucs;
foreach my $part (keys %hoh) {
my $val = $hoh{$part}{truc};
push #{ $trucs{$val} }, $part;
}
my #keys = #{ $trucs{78} };
or, more generally:
my %index;
foreach my $part (keys %hoh) {
my %data = %{ $hoh{$part} };
foreach my $key (keys %data) {
my $val = $data{$key};
push #{ $index{$key}{$val} }, $part;
}
}
my #keys = #{ $index{truc}{78} };
Can't with that data structure as is - There is no 'backwards' relationship from value to key without you creating it.
You've two options - run a search, or create an 'index'. Practically speaking, these are the same, just one saves the results.
my %index;
foreach my $key ( keys %hoh ) {
my $truc = $hoh{$key}{'truc'};
$index{$truc} = $key;
}
Note - won't do anything clever if the 'truc' numbers are duplicated - it'll overwrite. (Handling this is left as an exercise to the reader).
This solution is similar to those already posted, but it uses the each operator to process the original hash in fewer lines of code, and probably more quickly.
I have added the dump output only so that you can see the form of the structure that is built.
use strict;
use warnings;
my %hoh = (
T123456 => { bidule => '99', machin => '97', truc => '69' },
T431567 => { bidule => '02', machin => '01', truc => '03' },
T444444 => { bidule => '64', machin => '12', truc => '78' },
);
my %trucs;
while ( my ($key, $val) = each %hoh ) {
next unless defined( my $truc = $val->{truc} );
push #{ $trucs{$truc} }, $key ;
}
use Data::Dump;
dd \%trucs;
print "\n";
print "$_\n" for #{ $trucs{78} };
output
{ "03" => ["T431567"], "69" => ["T123456"], "78" => ["T444444"] }
T444444
If you can guarantee that the answer is unique, i.e. that there is never more than one element of the original hash that has a given value for the truc entry, or you are interested only in the last one found, then you can write this still more neatly
my %trucs;
while ( my ($key, $val) = each %hoh ) {
next unless defined( my $truc = $val->{truc} );
$trucs{$truc} = $key ;
}
print $trucs{78}, "\n";
output
T444444
Simplest of all, if there is always a truc entry in each second-level hash, and its values is guaranteed to be unique, then this will do the job
my %trucs = map { $hoh{$_}{truc} => $_ } keys %hoh;
print $trucs{78}, "\n";
with the output as above.

How do I read elements of a hash using foreach?

use Data::Dumper;
%hash = (
Key => {test => [[testvalue, 10], [testvalue, 20]]},
Key2 => {test => [[testvalue, 30], [testvalue, 40]]},
);
my $parm = $hash{Key}{test};
foreach my $test_p (#{$parm}) {
print Dumper $test_p;
}
It is not displaying in the way I expect.
A comma is missing at the end of the first line.
%hash = (
Key => {
test => [
[ testvalue , 10 ],
[ testvalue , 20 ]
]
},
Key2 => {
test => [
[ testvalue , 30 ],
[ testvalue , 40 ]
]
}
);
my $parm = $hash{Key}{test} ;
foreach my $test_p (#$parm) {
use Data::Dumper;
print Dumper $test_p;
}
You can try this:
my %hash = (
Key => {test => [['testvalue', 10], ['testvalue', 20]]},
Key2 => {test => [['testvalue', 30], ['testvalue', 40]]},
);
my $parm = $hash{Key}{test};
foreach my $test_p (#{$parm}) {
print Dumper $test_p;
}
foreach my $test (keys %hash) {
my $test1 = $hash{$test};
print Dumper $test;
foreach my $test2 (keys %{$test1}) {
print Dumper $test2;
my $test3 = $hash{$test}{$test2};
foreach my $test_p (#{$test3}) {
print Dumper #{$test_p};
}
}
}
Perhaps:
#/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
sub main{
my %hash = ( Key => { test => [[ "testvalue" , 10 ], [ "testvalue" ,20]] },
Key2 => { test => [[ "testvalue" , 30 ], [ "testvalue" ,40]] } );
foreach my $key (sort keys %hash){
my $parm = $hash{$key}{test};
print Dumper $_ foreach(#$parm);
}
}
main();
Outputs:
$VAR1 = [
'testvalue',
10
];
$VAR1 = [
'testvalue',
20
];
$VAR1 = [
'testvalue',
30
];
$VAR1 = [
'testvalue',
40
];

How can I force list context in Template Toolkit with RDBO?

I have a TT plugin that does the trivial unique ids:
sub get_unique_uid_tt {
my ( $classname, $o ) = #_;
my %h;
foreach my $item ( #{$o} ) {
unless ( exists $h{ $item->id } ) {
$h{ $item->id } = 1;
}
}
return keys %h;
}
where the template call is simply:
[% Namespace.get_unique_uid_tt( data.users ) %]
and "data" is an RDB Object, users being one of its relationships. I have verified that the ".users" returns a list in Perl directly, whether the relationship has one or many elements.
However, it appears that TT returns the element for single-element lists, while properly returning lists for multiple element.
I looked this up and found that you can force list context with ".list":
[% Namespace.get_unique_uid_tt( data.users.list ) %]
This does not work as intended for single-element lists, as a Data::Dumper revealed:
$VAR1 = [
{
'value' => 1,
'key' => '__xrdbopriv_in_db'
},
{
'value' => bless(
... snip ...
),
'key' => 'db'
},
{
'value' => '1',
'key' => 'id'
}
];
instead of the expected
$VAR1 = [
bless( {
'__xrdbopriv_in_db' => 1,
'id' => '1',
'db' => ... snip ...
}, 'DataClass' )
];
Is there any other simple way in TT to get a list of objects, even on single-element lists? (One approach is to rewrite the function, but one that does not would be preferable)
Found this on the TT mailing list:
http://lists.template-toolkit.org/pipermail/templates/2009-December/011061.html
seems like TT's ".list" has trouble converting objects to lists in general, not just RDBOs.
The suggestion is make a vmethod:
$Template::Stash::LIST_OPS->{ as_list } = sub {
return ref( $_[0] ) eq 'ARRAY' ? shift : [shift];
};
I added this to my context object (same idea):
$context->define_vmethod(
'list',
'as_list',
sub {
return ref( $_[0] ) eq 'ARRAY' ? shift : [shift];
},
);
It's not quite what you're after, but could you alter the TT plugin to handle both lists and single items?
sub get_unique_uid_tt {
my ( $classname, $o ) = #_;
my %h;
if (ref $o eq 'ARRAY') {
foreach my $item ( #{$o} ) {
unless ( exists $h{ $item->id } ) {
$h{ $item->id } = 1;
}
}
}
else {
return ($o->id);
}
return keys %h;
}