perl: Plotting data from multiple arrays - arrays

In my perl code, I have several arrays with the name 'a', 'b', 'c'.... 'e', 'f'. I am sending them as the argument while calling 'MyFunc'. There I want to plot any two arrays, for example, 'e' vs 'f'.
I tried in the following way (please look at the code), but I get a message that no data points are available in the line where my $gd = $graph->plot(\#e,\#f) or die $graph->error; command is being executed.
How to make it work?
MyFunc(
'a' => [0, 1,2,3,4],
'c' => [0, -1,2,-3,6],
'c' => [0, 2,4,2,5],
'd' => [0, 1,2,3,4],
'e' => [0, 9,2,1,7],
'f' => [-2, 5,-1,1,7],
'g' => [5, 1,8,-2,5],
);
sub MyFunc {
use GD::Graph::lines;
my $graph = GD::Graph::lines->new;
$graph->set(
x_label => 'X Label',
y_label => 'Y label',
title => 'Some simple graph',
y_max_value => 8,
y_tick_number => 8,
y_label_skip => 2
) or die $graph->error;
my $gd = $graph->plot(\#e,\#f) or die $graph->error;
open(IMG, '>file.gif') or die $!;
binmode IMG;
print IMG $gd->gif;
close IMG;
};

Passing an argument 'e' => [0,9,2,1,7] to a subroutine does not automatically create a variable called #e inside the subroutine. Your subroutine does do not do any processing of arguments whatsoever. Consider something like this to do what you seem to want:
sub MyFunc {
my %params = #_;
...
my $gd = $graph->plot( [$params{"e"}, $params{"f"}] ) ...
...
}

Related

How do I store an array as a value in a Perl hash?

I'm trying to create a hash in Perl, whose values are arrays. Something like:
my #array = split(/;/, '1;2');
my $hash = {'a' => #array};
Surprisingly, this reports (using Data::Dumper):
$VAR1 = {
'a' => '1',
'2' => undef
};
This page gives an example of storing an array in a hash by defining the array use square brackets, like:
my $hash = {'a' => ['1', '2']};
That works, but I'm getting my array from a call to the split method. What's magic about square brackets versus parentheses for defining an array, and how can I turn a "parentheses-array" into a 'square-brackets' array?
The values of hash (and array) elements are scalars, so you can't store an array into a hash.
The following are all equivalent:
my $hash = { a => #array };
my $hash = { 'a', #array };
my $hash = { 'a', $array[0], $array[1] };
my $hash = { a => $array[0], $array[1] => () };
A common solution is to store a reference to the array.
my #array = split(/;/, '1;2');
my $hash = { a => \#array }; # my $hash = { a => [ '1', '2' ] };
[ LIST ] similarly create an array, assigns LIST to it, then returns a reference to the array.

Hashes array elements copy

My array of hashes:
#cur = [
{
'A' => '9872',
'B' => '1111'
},
{
'A' => '9871',
'B' => '1111'
}
];
Expected result:
#curnew = ('9872', '9871');
Any simple way to get only the values of the first hash element from
this and assign it to an array?
Mind that hashes are unordered, so I take the word first to mean lexicographically first.
map { # iterate over the list of hashrefs
$_->{ # access the value of the hashref
(sort keys $_)[0] # … whose key is the first one when sorted
}
}
#{ # deref the arrayref into a list of hashrefs
$cur[0] # first/only arrayref (???)
}
The expression returns qw(9872 9871).
Assigning an arrayref to an array as in #cur = […] is probably a mistake, but I took it at face value.
Bonus perl5i solution:
use perl5i::2;
$cur[0]->map(sub {
$_->{ $_->keys->sort->at(0) }
})->flatten;
The expression returns the same values as above. This code is a bit longer, but IMO more readable because the flow of execution goes strictly from top to bottom, from left to right.
First your array have to be defined as
my #cur = (
{
'A' => '9872',
'B' => '1111'
},
{
'A' => '9871',
'B' => '1111'
}
);
Note the parenthesis
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dump qw(dump);
my #cur = (
{
'A' => '9872',
'B' => '1111'
},
{
'A' => '9871',
'B' => '1111'
}
);
my #new;
foreach(#cur){
push #new, $_->{A};
}
dump #new;
use Data::Dumper;
my #hashes = map (#{$_}, map ($_, $cur[0]));
my #result = map ($_->{'A'} , #hashes);
print Dumper \#result;

How to declare AoHoAoH?

I have this output from Dumper
'group' => {
'1104' => {
'a' => 1
},
'52202' => {
'b' => 1,
'c' => 1
},
'52201' => {
'c' => 1
},
'52200' => {
'c' => 1
}
},
which I assume is an Array of Hashes of Arrays of Hashes?
I would like to declare this structure my self.
Is there a way to do this, so next time I see such a complex structure, I can do that in no time? =)
Your output is a hash of hashes of hashes, with the first hash only containing a single element. The {} mark a hash reference, so you'd repeat your data structure thus, where the resulting $hohoh is a refrence to a HoHoH.
my $hohoh = {
'group' => {
'1104' => {
'a' => 1
},
'52202' => {
'b' => 1,
'c' => 1
},
'52201' => {
'c' => 1
},
'52200' => {
'c' => 1
}
},
};
print $hohoh->{group}{1104}{a}; # -> 1
I recommend reading the Perl Datastructures Cookbook.
Since the types of variables, and of hash values, can change in Perl, there isn't any way to "declare" a three-level hash the way you're probably thinking. You can instantiate an empty hashref into each key as it's created, which is a similar idea:
# First pass
my $data = {};
# Later...
$data->{group} = {};
# Still later...
$data->{group}->{1104} = {};
# Finally...
$data->{group}->{1104}->{a} = 1;
But you could just as easily simply fill in the data as you obtain it, allowing autovivification to do its thing:
my $data;
# Fill one piece of data... Perl creates all three hash levels now.
$data->{group}->{1104}->{a} = 1;
# Fill another piece of data, this one has two values in the "bottom" hash.
$data->{group}->{52202} = { b => 1, c => 2};
But there is no way (in plain Perl) to "enforce" that the values for any particular key contain hashes rather than strings or subroutine references, which is usually what is intended by declaration in languages with C-like type systems.

How can I delete empty arrays/refs from a Perl hash?

Lets say I have the following Perl hash:
%hash = (
'A' => {
'B' => ['C', 'D', 'E'],
'F' => { 'G' => [], 'H' => [] },
'I' => []
} );
and I'd like to get rid of the []'s to get the hash result below:
%hash = (
'A' => [
'B' => ['C', 'D', 'E'],
'F' => [ 'G', 'H', 'I' ]
]
)
(I hope I got my {} and [] balanced, my apologies if not, but) essentially I'd like to make it so that no empty arrays/ref's exist. I'm sure this is possible/simple, but I'm not sure whether delete() will work, or if there's a better method or a Perl module out there. Can someone steer me in the right direction?
It appears like your data might be nested arbitrarily, and you want to walk through it recursively, rewriting certain patterns to others. For that, I'd be using Data::Visitor.
use Data::Visitor::Callback;
use List::MoreUtils 'all';
my $visitor = Data::Visitor::Callback->new(
hash => sub {
my ($self, $href) = #_;
# fold hashrefs with only empty arrayrefs as values into arrayrefs
if (all { ref $_ eq 'ARRAY' && !#{ $_ } } values %{ $href }) {
return [ keys %{ $href } ];
}
# strip k/v pairs with an empty arrayref as a value
return {
map {
$_ => $href->{$_}
} grep {
ref $href->{$_} ne 'ARRAY' || #{ $href->{$_} }
} keys %{ $href }
};
},
);
my %new_hash = %{ $visitor->visit(\%hash) };
This just illustrates the basic approach I'd use, and happens to work for the example input you gave. It might need various tweaks depending on what you want to do in the corner-cases pointed out in the other comments.
[This should be a comment, but I need the formatting.]
Your question is puzzling. (1) By what principle does the I key (from the original hash) end up inside the list for the F key (in the expected hash)? (2) And what should happen if F were to contain stuff besides the empty array refs (see my addition to the original hash)?
my %hash_orig = (
'A' => {
'B' => ['C', 'D', 'E'],
'F' => {
'G' => [],
'H' => [],
'Z' => ['FOO', 'BAR'], # Not in the OP's original.
},
'I' => [],
},
);
my %hash_expected = (
'A' => [
'B' => ['C', 'D', 'E'],
'F' => [ 'G', 'H', 'I'], # Where should the Z info go?
],
);
Walking a hash (tree, whatever) is a technique that any programmer should know. rafl uses a visitor module, but in some cases I think the cure is almost worse than the disease.
Is your expected output what you intended? It seems different that what you said in the text, as FM says. I use his hash in my example.
It's pretty easy if you use a queue. You start with the top-level hash. Every time you run into a hash ref, you add it to the queue. When you run into an array ref, you check that it has values and delete that key if it doesn't. Everything else you leave alone:
#!perl
use strict;
use warnings;
use 5.010;
my %hash = ( # From FM
'A' => {
'B' => ['C', 'D', 'E'],
'F' => {
'G' => [],
'H' => [],
'Z' => ['FOO', 'BAR'], # Not in the OP's original.
},
'I' => [],
},
);
my #queue = ( \%hash );
while( my $ref = shift #queue ) {
next unless ref $ref eq ref {};
KEY: foreach my $key ( keys %$ref ) {
if( ref $ref->{$key} eq ref {} ) {
push #queue, $ref->{$key};
next KEY;
}
elsif( ref $ref->{$key} eq ref [] ) {
delete $ref->{$key} if #{$ref->{$key}} == 0;
}
}
}
use Data::Dumper;
print Dumper( \%hash );
My output is:
$VAR1 = {
'A' => {
'F' => {
'Z' => [
'FOO',
'BAR'
]
},
'B' => [
'C',
'D',
'E'
]
}
};
That output sounds more like what you are asking for, rather than the reorganization that you specify. Can you clarify the output?

How do I reference a Perl hash in an array in a hash?

This is the code snippet I am working with:
my %photo_details = (
'black_cat' => (
('size' => '1600x1200', 'position' => -25),
('size' => '1280x1024', 'position' => 25),
('size' => '800x600', 'position' => 0),
),
'race_car' => (
('size' => '1600x1200', 'position' => 10),
('size' => '800x600', 'position' => 5),
),
);
my $photo = 'black_cat';
foreach my $photo_detail ($photo_details{$photo})
{
my $size = $photo_detail{'size'};
my $position = $photo_detail{'position'};
print ("size = $size, position = $position\n");
}
What I am expecting to get is:
size = 1600x1200, position = -25
size = 1280x1024, position = 25
size = 800x600, position = 0
What I do get is:
Use of uninitialized value $size in concatenation (.) or string at C:\Test.pl line 23.
Use of uninitialized value $position in concatenation (.) or string at C:\Test.pl line 23.
size = , position =
The foreach statement is clearly wrong as not only are there no values for $size and $position, it has only gone through the loop once instead of three times. I have tried all sorts of variants of variable prefixes and found none that work.
What am I doing wrong?
Here is some updated code, with an explanation below:
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my %photo_details = (
'black_cat' => [
{'size' => '1600x1200', 'position' => -25},
{'size' => '1280x1024', 'position' => 25},
{'size' => '800x600', 'position' => 0},
],
'race_car' => [
{'size' => '1600x1200', 'position' => 10},
{'size' => '800x600', 'position' => 5},
],
);
print Dumper( %photo_details );
foreach my $name ( keys %photo_details ) {
foreach my $photo_detail ( #{ $photo_details{$name} } ) {
my $size = $photo_detail->{'size'};
my $position = $photo_detail->{'position'};
print Dumper( $photo_details{$photo} );
print ("size = $size, position = $position\n");
}
}
I've replaced some of your parentheses with square and curly brackets. In Perl, square brackets give you a reference to an anonymous array, and curly brackets denote a reference to an anonymous hash. These are called anonymous because there's no explicit variable name for the anonymous array or hash.
As Perl data structures make you store a reference to a hash rather than the actual hash, you need these to construct the references. You can do this in two steps like this:
my #array = ( 1, 2, 3 );
my $array_ref = \#array;
my %hash = ( 'one' => 1, 'two' => 2, 'three' => 3 );
my $hash_ref = \%hash_ref;
To get data out of $array_ref and $hash_ref, you need the -> operator:
print $array_ref->[0], "\n";
print $hash_ref->{one}, "\n";
You don't need the quotes inside of the {} when referencing a hash key, although some people consider quotes on a hash key to be good practice.
I added an example of iteration over the entire data structure as an example rather than just looking at one reference. Here's the first line:
foreach my $name ( keys %photo_details ) {
The keys method returns all of the keys in a hash, so that you can get them in order. The next line iterates over all of the photo_detail hashrefs in %photo_details:
foreach my $photo_detail ( #{ $photo_details{$photo} } ) {
The #{ $photo_details{$photo} } de-references the reference $photo_details{$photo} into an array, which you can iterate over it with foreach.
The last thing that I added is a call to Data::Dumper, a very useful module distributed with Perl that prints out data structures for you. This is very handy when building up data structures like this, as is its closely related cousin Data::Dumper::Simple. This module is unfortunately not distributed with Perl, but I prefer its output as it includes variable names.
For some further reading about how to build up complex data structures using references, check out perlreftut.
First of all, always start every script or module with:
use strict;
use warnings;
You will get more warning messages and sooner, which greatly helps debugging.
I cannot duplicate your error: when I put that code into a file and run it with no additional flags, I get: size = , position =. There is no $size variable in the code you printed, so the error message does not match.
Nevertheless, you are declaring your data structures incorrectly. Hashes and arrays can
only contain scalar values, not lists: so if you want to nest an array or
a hash, you need to make it a reference. See perldoc perldata, perldoc perldsc
and perldoc perlreftut for more about data structures and references.
my %photo_details = (
black_cat => [
{ size => '1600x1200', position => -25 },
{ size => '1280x1024', position => 25 },
{ size => '800x600', position => 0 },
],
race_car => [
{ size => '1600x1200', position => 10 },
{ size => '800x600', position => 5 },
],
);
foreach my $photo_detail (#{$photo_details{black_cat}})
{
my $size = $photo_detail->{size};
my $position = $photo_detail->{position};
print ("size = $size, position = $position\n");
}
There's really only one thing you have to worry about, and that's the top level of the data structure. After that, you just use the right indexing syntax for each level:
If you have a regular hash, you access the key that you want then line up the additional indices for each level after it:
%regular_hash = ...;
$regular_hash{$key}[$index]{$key2};
If you have a reference, you do almost the same thing, but you have to start off with the initial dereference with an arrow, ->, after the top-level reference. After that it's the same indexing sequence:
$hash_ref = ...;
$hash_ref->{$key}[$index]{$key2};
For all of the details, see Intermediate Perl where we explain reference syntax.

Resources