Iterating through an array of Class::Struct objects - perl

I'm using the Class::Struct module
and have two structures, Cat and Kitten, Cat is a mother cat, and one of the attributes of Cat should be an array of Kitten objects. I'm having difficulty getting this right.
If I save the kittens array as an object I cannot iterate through it as expected; I just get an array with one element which appears to be an array. If I save it as a reference I can not figure out how to deference it correctly.
#!/usr/bin/perl
use strict;
use warnings;
use Class::Struct;
use Data::Dumper;
struct Cat => [
name => '$',
age => '$',
kittens => '$',
];
struct Kitten => [
name => '$',
age => '$',
];
my $lil_kitten = Kitten->new( name => 'Lil Socks', age => 2 );
my $big_kitten = Kitten->new( name => 'Big Socks', age => 3 );
my $old_kitten = Kitten->new( name => 'Old Socks', age => 4 );
my #kitten_array = ( $lil_kitten, $big_kitten, $old_kitten );
my $kitten_count = scalar #kitten_array;
print "There were $kitten_count kittens\n";
foreach my $k ( #kitten_array ) {
bless $k, 'Kitten';
print "Kitten: ", $k->name, " is ", $k->age, " years old.\n";
print Dumper($k), "\n";
}
my $cat = Cat->new( name => 'Socks' );
$cat->age(17);
#$cat->kittens(#kitten_array);
$cat->kittens(\#kitten_array);
my $ref_type = $cat->kittens;
print "Ref type: ", $ref_type, "\n\n\n\n";
#$kitten_count = scalar #$cat->kittens;
#print "There were $kitten_count kittens\n";
print "Once a cat called ", $cat->name, " who was ", $cat->age, "\n";
#foreach my $kat(#$cat->kittens) {
foreach my $kat($cat->kittens) {
bless $kat, 'Kitten';
print "Kitten: ", $kat->name, " is ", $kat->age, " years old.\n";
print Dumper($kat), "\n";
}

You should initialize 'kittens' to be an array (using '#') and use curly brackets for precedence in the dereference. I have flagged the changed lines with the comment # CHANGED.
#!/usr/bin/perl
use strict;
use warnings;
use Class::Struct;
use Data::Dumper;
struct Cat =>
[
name => '$',
age => '$',
kittens => '#', # CHANGED
];
struct Kitten =>
[
name => '$',
age => '$',
];
my $lil_kitten = Kitten->new( name => 'Lil Socks', age=> 2);
my $big_kitten = Kitten->new( name => 'Big Socks', age=> 3);
my $old_kitten = Kitten->new( name => 'Old Socks', age=> 4);
my #kitten_array = ($lil_kitten, $big_kitten, $old_kitten);
my $kitten_count = scalar #kitten_array;
print "There were $kitten_count kittens\n";
foreach my $k(#kitten_array) {
bless $k, 'Kitten';
print "Kitten: ", $k->name, " is ", $k->age, " years old.\n";
print Dumper($k), "\n";
}
my $cat = Cat->new( name => 'Socks');
$cat->age(17);
#$cat->kittens(#kitten_array);
$cat->kittens(\#kitten_array);
my $ref_type = $cat->kittens;
print "Ref type: ", $ref_type, "\n\n\n\n";
$kitten_count = scalar #{$cat->kittens}; # CHANGED
print "There were $kitten_count kittens\n";
print "Once a cat called ", $cat->name, " who was ", $cat->age, "\n";
#foreach my $kat(#$cat->kittens) {
foreach my $kat($cat->kittens) {
bless $kat, 'Kitten';
print "Kitten: ", $kat->name, " is ", $kat->age, " years old.\n";
print Dumper($kat), "\n";
}
The problem with #$cat->kittens is that it first dereferences $cat to become an array, then tries to call the method kittens() on that array. This involves Perl trying to interpret #$cat as a class name by turning it into a string - that is, scalar #$cat, which is the size of the array. You end up calling "3"->kittens().

Related

Issue in Printing data from a hash table in perl

I am trying to process the data in a single file . i have to read the file and create a hash structure,get the value of fruitname append it to fruitCount and fruitValue and delete the line fruitName and write the entire output after the change is done.Given below is the content of file.
# this is a new file
{
date 14/07/2016
time 11:15
end 11:20
total 30
No "FRUITS"
Fruit_class
{
Name "fruit 1"
fruitName "apple.fru"
fruitId "0"
fruitCount 5
fruitValue 6
}
{
Name "fruit 2"
fruitName "orange.fru"
fruitId "1"
fruitCount 10
fruitValue 20
}
}
I tried with following code :
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my %hash_table;
my $name;
my $file = '/tmp/fruitdir/fruit1.txt';
open my $fh, "<", $file or die "Can't open $file: $!";
while (<$fh>) {
chomp;
if (/^\s*fruitName/) {
($name) = /(\".+\")/;
next;
}
s/(fruitCount|fruitValue)/$name\.$1/;
my ($key, $value) = split /\s+/, $_, 2;
$hash_table{$key} = $value;
}
print Dumper(\%hash_table);
This is not working . I need to append the value of fruitname and print the the entire file content as output. Any help will be appreciated.Given below is the output that i got.
$VAR1 = {
'' => undef,
'time' => '11:15 ',
'date' => '14/07/2016',
'{' => undef,
'#' => 'this is a new file',
'total' => '30 ',
'end' => '11:20 ',
'No' => '"FRUITS"',
'Fruit_class' => undef,
'}' => undef
};
Expected hash as output:
$VAR1 = {
'Name' => '"fruit 1"',
'fruitId' => '"0" ',
'"apple_fru".fruitValue' => '6 ',
'"apple_fru".fruitCount' => '5'
'Name' => '"fruit 2"',
'fruitId' => '"0" ',
'"orange_fru".fruitValue' => '10 ',
'"orange_fru".fruitCount' => '20'
};
One word of advice before I continue:
Document your code
There are several logic errors in your code which I think you would have recognized if you wrote down what you thought each line was supposed to do. First, write down the algorithm that you would like to implement, then document how each step in the code implements a step in the algorithm. At the end you'll be able to see what you missed, or what part is not working.
Here are the errors that I see
You aren't ignoring lines that you shouldn't be parsing. For example, you're grabbing the '}' and '{' lines.
You aren't actually storing the name of the fruit. You grab it, but immediately start the next loop without storing it.
You're not keeping track of each structure. You need to start a new structure for each fruit.
Do you really want to keep the double quotes in the values?
Other things to worry about:
Are you guaranteed that the list of attributes is in that order? For example, can Name come last?
Here's some code which does what I think you want.
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my %hash_table;
my $name;
my #fruit;
my $file = '/tmp/fruitdir/fruit1.txt';
open my $fh, "<", $file or die "Can't open $file: $!";
while (<$fh>) {
chomp;
# save hash table if there's a close bracket, but
# only if it has been filled
if ( /^\s*}\s*$/ ) {
next unless keys %hash_table;
# save COPY of hash table
push #fruit, { %hash_table };
# clear it out for the next iteration
%hash_table = ();
}
# only parse lines that start with Name or fruit
next unless
my ( $key, $value ) =
/^
# skip any leading spaces
\s*
# parse a line beginning with Name or fruitXXXXX
(
Name
|
fruit[^\s]+
)
# need space between key and value
\s+
# everything that follows is a value. clean up
# double quotes in post processing
(.*)
/x;
# remove double quotes
$value =~ s/"//g;
if ( $key eq 'Name' ) {
$name = $value;
}
else {
$key = "${name}.${key}";
}
$hash_table{$key} = $value;
}
print Dumper \#fruit;
and here's the output:
$VAR1 = [
{
'fruit 1.fruitValue' => '6',
'fruit 1.fruitName' => 'apple.fru',
'Name' => 'fruit 1',
'fruit 1.fruitCount' => '5',
'fruit 1.fruitId' => '0'
},
{
'fruit 2.fruitName' => 'orange.fru',
'fruit 2.fruitId' => '1',
'fruit 2.fruitCount' => '10',
'fruit 2.fruitValue' => '20',
'Name' => 'fruit 2'
}
];

get nbest key-value pairs hash table in Perl

I have this script that use a hash table:
#!/usr/bin/env perl
use strict; use warnings;
my $hash = {
'cat' => {
"félin" => '0.500000',
'chat' => '0.600000',
'chatterie' => '0.300000'
'chien' => '0.01000'
},
'rabbit' => {
'lapin' => '0.600000'
},
'canteen' => {
"ménagère" => '0.400000',
'cantine' => '0.600000'
}
};
my $text = "I love my cat and my rabbit canteen !\n";
foreach my $word (split "\s+", $text) {
print $word;
exists $hash->{$word}
and print "[" . join(";", keys %{ $hash->{$word} }) . "]";
print " ";
}
For now, I have this output:
I love my cat[chat;félin;chatterie;chien] and my rabbit[lapin] canteen[cantine;ménagère] !
I need to have the nbest key value according to the frequencies (stored in my hash). For example, I want to have the 3 best translations according to the frequencies like this:
I love my cat[chat;félin;chatterie] and my rabbit[lapin] canteen[cantine;ménagère] !
How can I change my code to take into account the frequencies of each values and also to print the nbest values ?
Thanks for your help.
The tidiest way to do this is to write a subroutine that returns the N most frequent translations for a given word. I have written best_n in the program below to do that. It uses rev_nsort_by from List::UtilsBy to do the sort succinctly. It isn't a core module, and so may well need to be installed.
I have also used an executable substitution to modify the string in-place.
use utf8;
use strict;
use warnings;
use List::UtilsBy qw/ rev_nsort_by /;
my $hash = {
'cat' => {
'félin' => '0.500000',
'chat' => '0.600000',
'chatterie' => '0.300000',
'chien' => '0.01000',
},
'rabbit' => {
'lapin' => '0.600000',
},
'canteen' => {
'ménagère' => '0.400000',
'cantine' => '0.600000',
}
};
my $text = "I love my cat and my rabbit canteen !\n";
$text =~ s{(\S+)}{
$hash->{$1} ? sprintf '[%s]', join(';', best_n($1, 3)) : $1;
}ge;
print $text;
sub best_n {
my ($word, $n) = #_;
my $item = $hash->{$word};
my #xlate = rev_nsort_by { $item->{$_} } keys %$item;
$n = $n > #xlate ? $#xlate : $n - 1;
#xlate[0..$n];
}
output
I love my [chat;félin;chatterie] and my [lapin] [cantine;ménagère] !

order hash perl using another array

Im trying to sort a Hash using a list/array
my $hash = { cta => '01340031810312074443',
ttr => '001',fil => '0000',
ref => '0000',
mef => '0000000000000060000',
mch => '0000000000000000000',
nli => '00000000',
tdi => 'V',
ndi => '006126952',
tdip => 'V',
ndip => '006126952',
};
#order = qw(cta ttr fil ref mef mch nli tdi ndi tdip ndip);
We know Perl dont save orders in hash but I need to print in that order. How can I do that?
Thanks
If you just want to print the values and not the keys, you can also use a hash slice:
use feature 'say';
say join "\t", #hash{#order};
for my $key (#order) {
print $key . ": " . $hash->{$key} . "\n";
}
Try this:
for (#order) {
print $_, " => ", $hash->{$_}, "\n";
}

Perl - Class::Struct Deferencing array

use Class::Struct;
struct (TimingStruct => {
_timingSense => '$',
_timingType => '$',
_relatedPin => '$',
_whenCond => '$'
});
struct (OutPinStruct => {
_outPinName => '$',
_outFunction => '$',
_timingarray => '#', #_timingarc => 'TimingStruct'
});
my #tarray = ();
my $t;
$t = TimingStruct->new(_timingSense => 'Unate',
_timingType => 'Wave',
_relatedPin => 'CO',
_whenCond => 'A ^ B'
);
push(#tarray, $t);
$t = TimingStruct->new(_timingSense => 'Combinational',
_timingType => 'Rising',
_relatedPin => 'ICO',
_whenCond => 'A ^ B ^ CI'
);
push(#tarray, $t);
my $op = OutPinStruct->new(_outPinName => "CO",
_outFunction => "A ^ B ^ CI",
_timingarray => \#tarray);
print $op->_outPinName . "\n";
print $op->_outFunction . "\n";
print $op->_timingarray . "\n";
my $t = ${${$op->_timingarray}[0]}[0];
print "\$t = \$op->_timingarray = $t->_timingSense() \n";
my #t = {$op->_timingarray};
print "\#t = \#{\$op->_timingarray} = $$t[1] \n";
Every output pin can have many timing-arcs and the OutPinStruct has a array to hold the timing-arcs. I'm not sure about de-referencing arrays(_timingarray) could someone tell me what is it that I'm doing wrongly?
Thanks.
$op->_timingarray is a "list of hashes". In general the keys to a hash are unordered and you cannot lookup the hash values through a numbered index. The elements you can access are
$op->_timingarray->[0]{'TimingStruct::_whenCond'}
$op->_timingarray->[0]{'TimingStruct::_timingSense'}
$op->_timingarray->[0]{'TimingStruct::_relatedPin'}
$op->_timingarray->[0]{'TimingStruct::_timingType'}
$op->_timingarray->[1]{'TimingStruct::_whenCond'}
$op->_timingarray->[1]{'TimingStruct::_timingSense'}
$op->_timingarray->[1]{'TimingStruct::_relatedPin'}
$op->_timingarray->[1]{'TimingStruct::_timingType'}

Recursively printing data structures in Perl

I am currently learning Perl. I have Perl hash that contains references to hashes and arrays. The hashes and arrays may in turn contain references to other hashes/arrays.
I wrote a subroutine to parse the hash recursively and print them with proper indentation. Though the routine works as expected, my instructor was not convinced about the readability and elegance of the below code.
I would really appreciate to get the views of Perl experts here on possible optimization of the below code.
Here is my complete code snippet..
# Array of Arrays
$ref_to_AoA = [
[ "fred", "barney" ],
[ "george", "jane", "elroy" ],
[ "homer", "marge", "bart" ],
];
#Array of Hashes
$ref_to_AoH = [
{
husband => "barney",
wife => "betty",
son => "bamm bamm",
},
{
husband => "george",
wife => "jane",
son => "elroy",
},
];
# Hash of Hashes
$ref_to_HoH = {
flintstones => {
husband => "fred",
pal => "barney",
},
jetsons => {
husband => "george",
wife => "jane",
"his boy" => "elroy", # Key quotes needed.
},
simpsons => {
husband => "homer",
wife => "marge",
kid => "bart",
},
};
# Hash which contains references to arrays and hashes
$finalHash = {
'arrayofArrays' => $ref_to_AoA,
'arrayofHash' => $ref_to_AoH,
'hashofHash' => $ref_to_HoH,
};
$string = str($finalHash);
print "$string\n";
#------------------------------------------------------------------
sub str {
my $hash = shift;
my ($space, $newline, $delimiter) = #_;
$space = "" unless (defined $space);
$newline = "\n\n\n" unless (defined $newline);
$delimiter = "\n--------------------------------------------" unless (defined $delimiter);
my $str = "";
for (sort keys %{$hash}) {
my $value = $hash->{$_};
$str .= "$newline$space$_ == $value$delimiter";
$str .= recurseErrors($value,$space);
}
$str;
}
#------------------------------------------------------------------
sub recurseErrors {
my $str;
my ($value,$space) = #_;
my $ref = ref $value;
if ($ref eq 'ARRAY') {
my $i = 0;
my $isEmpty = 1;
my #array = #$value;
$space .= "\t";
for my $a (#array) {
if (defined $a) {
$isEmpty = 0;
$str .= "\n$space$_\[$i\] :";
$str .= recurseErrors($a,$space);
}
$i++;
}
$str .= "= { }" if ($isEmpty);
} elsif ($ref eq 'HASH') {
$space .= "\t";
for my $k (sort keys %$value) {
if ( ( ref($value->{$k}) eq 'HASH') || (ref $value->{$k} eq 'ARRAY') ) {
my $val = $value->{$k};
$str .= "\n\n$space$k == ";
$str .= "$val";
}
else {
$str .= "\n$space$k == ";
}
$str .= recurseErrors($value->{$k},$space);
}
# we have reached a scalar (leaf)
} elsif ($ref eq '') {
$str .= "$value";
}
$str
}
#------------------------------------------------------------------
Output:
arrayofArrays == ARRAY(0x9d9baf8)
--------------------------------------------
arrayofArrays[0] :
arrayofArrays[0] :fred
arrayofArrays[1] :barney
arrayofArrays[1] :
arrayofArrays[0] :george
arrayofArrays[1] :jane
arrayofArrays[2] :elroy
arrayofArrays[2] :
arrayofArrays[0] :homer
arrayofArrays[1] :marge
arrayofArrays[2] :bart
arrayofHash == ARRAY(0x9d9bba8)
--------------------------------------------
arrayofHash[0] :
husband == barney
son == bamm bamm
wife == betty
arrayofHash[1] :
husband == george
son == elroy
wife == jane
hashofHash == HASH(0x9da45f8)
--------------------------------------------
flintstones == HASH(0x9d9bb48)
husband == fred
pal == barney
jetsons == HASH(0x9d9bbf8)
his boy == elroy
husband == george
wife == jane
simpsons == HASH(0x9d9bc48)
husband == homer
kid == bart
wife == marge
Always use use strict;
To be a good boy, use use warnings as well.
The names you use for subroutines should make it obvious what the subroutine does. "recurseErrors" kind of violates that principle. Yes, it does recurse. But what errors?
On the first line of each subroutine you should declare and initialize any parameters. recurseErrors first declares $str and then declares its parameters.
Don't mix shift and = #_ like you do in str()
You might consider breaking up what is now called recurseErrors into specialized routines for handling arrays and hashes.
There's no need to quote variables like you do on lines 99 and 109.
Apart from that I think your instructor had a bad day that day.
maybe Data::Dumper is what you want:
use Data::Dumper;
$str = Dumper($foo);
print($str);
If you are new to perl, I'd recommend running your code through perl-critic (there is also a script you can install from CPAN, normally I use it as a test so it gets run from the command line whenever I do "make test"). In addition to its output, you might want to break up your functions a bit more. recurseErrors has three cases that could be split into sub functions (or even put into a hash of ref-type to sub-function ref).
If this were a production job, I'd use Data::Dumper, but it sounds like this is homework, so your teacher might not be too pleased.
Here is one simple example why your code is not easily readable:
$delimiter = "\n--------------------------------------------" unless (defined $delimiter);
You could use the defined or operator:
$delimiter //= "\n" . '-' x 44;
If you are worried about earlier Perls:
defined $delimeter or $delimeter = "\n" . '-' x 44;
Conditionals going off the right margin are enough of a turn-off for me not to read the rest of the code.
My guess is that he doesn't like that you
expect a hash in the str function.
call the same function to print arrays as hashes, despite that there appears to be no common function between them.
allow various ways to call str, but it never figures into the final result.
allow configurable space to be passed in to the root function, but have a tab hardcoded in the recursive function.
omit undefined values that actually hold a place in the arrays
Those are issues that I can see, pretty quickly.
You could have separated out the code blocks that dealt with arrays, and hashes.
sub recurse{
...
recurse_A(#_) if $ref eq 'ARRAY';
recurse_H(#_) if $ref eq 'HASH';
...
}
sub recurse_A{ ... }
sub recurse_H{ ... }
I would recommend starting out your subroutines like this, unless you have a real good reason for doing otherwise.
sub example{
my( $one, $two, $three, $optional_four ) = #_;
( If you do it like this then Komodo, at least, will be able to figure out what the arguments are to your subroutine )
There is rarely any reason to put a variable into a string containing only the variable.
"$var" eq $var;
The only time I can think I would ever do that is when I am using an object that has an overloaded "" function, and I want to get the string, without also getting the object.
package My_Class;
use overload
'""' => 'Stringify',
;
sub new{
my( $class, $name ) = #_;
my $self = bless { name => $name }, $class;
return $self;
}
sub Stringify{
my( $self ) = #_;
return $self->{name};
}
my $object = My_Class->new;
my $string = "$object";
I've struggled with this same problem before, and found my way here. I almost used a solution posted here, but found a more suitable one (for me anyway). Read about Depth First Recursion here.
The sub in the above article works perfectly with a reference containing other Hashes, Arrays, or Scalars. It did not print Hash key names, though, so I slightly modified it:
#!/usr/bin/perl
#
# See:
#
# http://perldesignpatterns.com/?DepthFirstRecursion
#
use strict;
use warnings;
my %hash = (
'a' => {
'one' => 1111,
'two' => 222,
},
'b' => [ 'foo', 'bar' ],
'c' => 'test',
'd' => {
'states' => {
'virginia' => 'richmond',
'texas' => 'austin',
},
'planets' => [ 'venus','earth','mars' ],
'constellations' => ['orion','ursa major' ],
'galaxies' => {
'milky way' => 'barred spiral',
'm87' => 'elliptical',
},
},
);
&expand_references2(\%hash);
sub expand_references2 {
my $indenting = -1;
my $inner; $inner = sub {
my $ref = $_[0];
my $key = $_[1];
$indenting++;
if(ref $ref eq 'ARRAY'){
print ' ' x $indenting,'ARRAY:';
printf("%s\n",($key) ? $key : '');
$inner->($_) for #{$ref};
}elsif(ref $ref eq 'HASH'){
print ' ' x $indenting,'HASH:';
printf("%s\n",($key) ? $key : '');
for my $k(sort keys %{$ref}){
$inner->($ref->{$k},$k);
}
}else{
if($key){
print ' ' x $indenting,$key,' => ',$ref,"\n";
}else{
print ' ' x $indenting,$ref,"\n";
}
}
$indenting--;
};
$inner->($_) for #_;
}
#use strict ;
use warnings ;
# use module
use XML::Simple;
use Data::Dumper;
#debug print "START SCRIPT " ;
my $fileToParse = 'C:/Temp/CDIP/scripts/perl/nps_all_workflows.xml' ;
# create object
my $objXml= new XML::Simple;
# read XML file
my $data = $objXml->XMLin("$fileToParse");
# #debug print "\n FirstLevel is " . $objXml->{'POWERMART'} ;
my $level = 1 ;
#
printHashKeyValues ($data ) ;
sub printHashKeyValues
{
$level ++ ;
my $refHash = shift ;
my $parentKey = shift ;
my $parentValue = shift ;
while( my ($key, $value) = each %$refHash)
{
if ( defined ( $key ) )
{
if ( ref ($refHash->{"$key"}) eq 'HASH' )
{
my $newRefHash = $refHash->{"$key"} ;
#debug print " \n The key is a hash " ;
printHashKeyValues ($newRefHash , $key , $value) ;
}
if ( ref ($refHash->{"$key"}) eq 'ARRAY' )
{
#debug print " \n the key is an ARRAY " ;
printArrayValues ( $refHash->{"$key"} ) ;
}
} #eof if ( defined ( $key ))
if ( defined ( $value) )
{
if ( ref ($refHash->{"$value"}) eq 'HASH' )
{
my $newRefHash = $refHash->{"$value"} ;
#debug print " \n The value is a hash " ;
printHashKeyValues ($newRefHash , $key , $value) ;
}
if ( ref ($refHash->{"$value"}) eq 'ARRAY' )
{
#debug print " \n the value is an ARRAY " ;
printArrayValues ( $refHash->{"$value"} ) ;
}
} #eof if defined ( $value )
#debug print "\n key: $key, value: $value.\n";
} #eof while
} #eof sub
sub printArrayValues
{
my $arrRef = shift ;
my #array = #$arrRef;
my $parrentArrayElement = shift ;
#debug print "printArrayValues CALLED " ;
foreach my $arrayElement ( #array )
{
if (defined ( $arrayElement ) )
{
if ( ref ($arrayElement) eq 'HASH' )
{
#debug print " \n The \$arrayElement is a hash FROM THE ARRAY " ;
printHashKeyValues ($arrayElement ) ;
} #eof if
if ( ref ($arrayElement) eq 'ARRAY' )
{
#debug print " \n The \$arrayElement is a ARRAY FROM THE ARRAY " ;
printArrayValues ($arrayElement ) ;
} #eof if
#debug print "\n \$arrayElement is $arrayElement " ;
} #eof if ( defined ( $arrayElement ) )
} #eof foreach
} #eof sub
# #debug print output
##debug print Dumper($data);
1 ;