Can't get values when looping over an array of arrays - perl

I created an array like so:
while(#results = $execute->fetchrow())
{
my $active = 'true';
if($results[1] == 0)
{
$active = 'false';
}
my #campaign = ($results[0], $active);
push(#campaign_names, #campaign);
}
Later, when I need to access the name of the campaign (which is the first element of the campaign array), I can't seem to extract it. What is the proper syntax?
foreach $campaign (#campaign_names)
{
print ????;
}
Thanks!

The problem is you're pushing an array onto the end of #campaign_names, when what you want is an array reference. Here's how I'd write it:
while(#results = $execute->fetchrow())
{
my $active = $results[1] ? 'true' : 'false';
push #campaign_names, [ $results[0], $active ];
}
# later
foreach my $campaign( #campaign_names )
{
my $name = $campaign->[0];
my $active = $campaign->[1];
}
I've cleaned it up a bit by using a ternary conditional (?:) to figure out the value of $active. The [ ... ] constructs an anonymous array reference (a scalar pointing to an array) which is then pushed onto #campaign_names.
When we loop over those later, two important things to notice are that we use my in the loop variable to keep it local to the loop block, and that we use -> to dereference the elements in the array pointed to by the array reference.

That's not creating an array of arrays. my #campaign = ($results[0], $active); push(#campaign_names, #campaign); flattens and pushes $results[0] and $active into the #campaign_names array. Instead, push an arrayref:
my #campaign = ($results[0], $active);
push(#campaign_names, \#campaign);
or
my $campaign = [$results[0], $active];
push(#campaign_names, $campaign);
Arrays can only hold scalar values.
You'll want to refer to perldsc as you learn (perldoc perldsc, http://perldoc.perl.org/perldsc.html)

Related

How to split string by comma into a hash in Perl?

I have the following code:
my #logs = split(",",$opts->{"logs"});
$opt_href->{"logs"} = \#logs;
It basically splits the $opts->{"logs"} by comma and keeps the array ref. Later I need to check if string exists in the $opt_href->{"logs"} array. Looking at this topic, I see that it's recommended to keep a hash, instead of array. I could just do:
my %logs;
for each my $log (split(",",$opts->{"logs"})) {
$logs{$log} = 1;
}
$opt_href->{"logs"} = \%logs;
Is there a better way to do this? Maybe a one/two liners?
my %logs = map { $_ => 1 } split /,/, $opts->{logs};
$opt_href->{logs} = \%logs;
Or, using the anonymous hash reference, constructed by { }
$opt_href->{logs} = { map { $_ => 1 } split /,/, $opts->{logs} };

How to dereference entries in a hash of arrays of hashes

I have a data structure that was built like so:-
$ICVDWKey = "($LLX $LLY) ($URX $URY)";
...
push #{$ICVDWStats{$ICVDWKey}}, {
ICVDensity=>$Density,
ICVLayerArea=>$LayerArea,
ICVWindowArea=>$WindowArea
};
I can dereference its contents like so...
foreach $ICVDWKey (#AllICVDWCoords) {
foreach (#{$ICVDWStats{$ICVDWKey}}) {
$ICVDensity = $_->{ICVDensity};
$ICVLayerArea = $_->{ICVLayerArea};
$ICVWindowArea = $_->{ICVWindowArea};
...
}
}
...and everything is good. However, I am running into problems when another data structure is built the same way and I need to check its contents when looping through the original data structure, above. Here is an example...
foreach $ICVDWKey (#AllICVDWCoords) {
foreach (#{$ICVDWStats{$ICVDWKey}}) {
$ICVDensity = $_->{ICVDensity};
$ICVLayerArea = $_->{ICVLayerArea};
$ICVWindowArea = $_->{ICVWindowArea};
...
if (exists ($ICC2DWStats{$ICVDWKey})) {
$ICC2Density = $_->{ICC2Density};
$ICC2LayerArea = $_->{ICC2LayerArea};
$ICC2WindowArea = $_->{ICC2WindowArea};
...
}
}
}
I know the if exists $ICVDWKey matching is working properly, but I cannot cleanly dereference the contents of the ICC2DWStats hash data. What is the proper what to retrieve the ICC2* data when $ICVDWKey keys match between the two data structures? I am sure it is the $_ in the ICC2* references, but I do not know what should be used instead.
Thanks!
Instead of using $_ which represents a structure other than the $ICC2DWStats hashref which you want, you need to explicitly specify the hash and key of the actual hasn you want to extract from:
for $ICVDWKey (#AllICVDWCoords) {
for (#{$ICVDWStats{$ICVDWKey}}) {
$ICVDensity = $_->{ICVDensity};
$ICVLayerArea = $_->{ICVLayerArea};
$ICVWindowArea = $_->{ICVWindowArea};
...
if (exists ($ICC2DWStats{$ICVDWKey})) {
$ICC2Density = $ICC2DWStats->{$ICVDWKey}{ICC2Density};
$ICC2LayerArea = $ICC2DWStats->{$ICVDWKey}{ICC2LayerArea};
$ICC2WindowArea = $ICC2DWStats->{$ICVDWKey}{ICC2WindowArea};
...
}
}
}
Note that you should be using use strict; and use warnings;.

Access an array item in Perl after generating that array

Hello I want to access an specific array item based in a condition previously checked. I leave the code here:
elsif (scalar(#{$boss->bosses}) > 1) {
foreach my $pa (#{$boss->bosses}) {
my $p = My::Model::Group->new(id => $pa->group_id);
push(#$groups, $p);
$valid_pass = 1 if ($pa->checkPassword($self->param('password')));
}
if ($valid_pass) {
my $pa_id = $pa->id;
my $pa_partner_id = $pa->group_id;
}
else {
}
}
What I want to do is, if that if in the array that comes, I check if the password is correct, so if it's correct, then I want to take the id and the group_id of the array item to use it in a function to be able to log them in.
Your for loop is doing two things at once: producing a list of My::Model::Group objects in #$groups, and finding the first boss whose password checks out.
I suggest that you split them up into two clear operations, and the List::Util modules first operator is ideal for the second task
Here's how it would look. I've extracted the result of the method call $boss->bosses into a variable $bosses to avoid repeated calls to the method
Note that you don't need to apply scalar to an array when checking its size. The > and all the other comparators impose scalar context anyway
I've taken much of my code from your question, and I'm a little concerned that you extract values for $pa_id and $pa_partner_id and then just discard them. But I imagine that you know what you really want to do here
use List::Util 'first';
my $bosses = $boss->bosses;
if ( ... ) {
...;
}
elsif ( #$bosses > 1 ) {
#$groups = map { My::Model::Group->new( id => $_->group_id ) } #$bosses;
my $password = $self->param( 'password' );
my $pa = first { $_->checkPassword( $password ) } #$bosses;
if ( $pa ) {
my $pa_id = $pa->id;
my $pa_partner_id = $pa->group_id;
}
else {
...;
}
}

Array of hashes not being passed by ref to sub

I have a setter sub setAssignmentStatus which takes an array of hashes (AoH from here on) and another parameter (do not concern yourself with this as that part works), and does something iterating through the AoH to set another entry in each hash element. It does not return anything because I want to use the same AoH object with the added entries after it is pulled through the setter sub and not construct a whole new AoH and repopulate the entries. Here is the setter:
sub setAssignmentStatus
{
my $fileFlatArySclr = $_[0];
my $cfgFile = $_[1];
#here I convert the AoH from the scalar necessary for the sub to its native form
my #fileFlatAry = #$fileFlatArySclr;
#this works, don't worry
my %cfgVarHash = getConfigVars($cfgFile);
foreach my $fileVarHashSclr(#fileFlatAry)
{
#convert each AoH entry from scalar necessary for iteration to native hash
my %varHash = %$fileVarHashSclr;
my $varName = $varHash{'VAR_NAME'};
my $asgnLineCnt = $varHash{'ASGN_CNT'};
my $asgnSts;
my $fileAsgnSts;
my $cfgAsgnSts;
if($asgnLineCnt > 0) { $fileAsgnSts = 1; } else { $fileAsgnSts = 0; }
my $cfgAsgnLine = $cfgVarHash{$varName};
if($cfgAsgnLine ne undef) { $cfgAsgnSts = 1; } else { $cfgAsgnSts = 0; }
$asgnSts = $fileAsgnSts.$cfgAsgnSts;
#debug to make sure $asgnSts is not null in the first place (it is not!)
print "\n*** setting ASGN_STUS of ".$varName." to ".$asgnSts;
#Here we set ASGN_STUS for every iteration
$varHash{'ASGN_STUS'} = $asgnSts;
}
}
It is called as follows:
setAssignmentStatus(\#fileFlatAry, $cfgFile);
However, after sending the #fileFlatAry AoH through setAssignmentStatus, each element hash does not contain an ASGN_STUS entry. Why is that and how can I fix it?
My suspicion is that I am doing something wrong with the \ modifier, which is how I am getting the data structure to be passed as a scalar parameter to the sub but I am not sure.
You modify %varHash instead of modyfing the referenced hash. Stop copying everything into local variables and modyfying the local variables.
$varHash{'ASGN_STUS'} = ...;
should be
$fileVarHashSclr->{'ASGN_STUS'} = ...;
I wouldn't do my #fileFlatAry = #$fileFlatArySclr; either. Pure waste.

How can I set the order of Zend Form Elements and avoid duplicates

In Zend Form, if two elements have the same order, then Zend will totally ignores the second element (instead of displaying it under the first). Take the following code as an example. Notice that the City and Zip Code elements have the same order of 4
$address = new Zend_Form_Element_Textarea('address');
$address->setLabel('Address')
->setAttrib('cols', 20)
->setAttrib('rows', 2)
->setOrder(3)
;
$city = new Zend_Form_Element_Text('city');
$city->setLabel('City')
->setOrder(4)
;
$postal = new Zend_Form_Element_Text('postal');
$postal->setLabel('Zip Code')
->setOrder(4);
When this form renders, the Zip Code element is nowhere to be found.
If I want to set elements like a buttons dynamically, but tell it to render at the end of the form, how would I do this and not run into the problem of having two elements with the same order?
public function addSubmitButton($label = "Submit", $order = null)
{
$form_name = $this->getName();
// Convert Label to a lowercase no spaces handle
$handle = strtolower(str_replace(" ","_",$label));
$submit = new Zend_Form_Element_Submit($handle);
$submit->setLabel($label)
->setAttrib('id', $form_name . "_" . $handle)
;
///////// Set the button order to be at the end of the form /////////
$submit->setOrder(??????);
$this->addElement($submit);
}
If you really need to use the setOrder() method, I'd work with order numbers 10, 20, 30, 40, ... This way it will be easy to add elements in between already set Elements.
Furthermore, in order to avoid using order-numbers twice, you could use an array, where you store all the numbers from 1 to X. Whenever you set an order number, you set it via a method called getOrderNumberFromArray() which returns the next higher or lower order number still available in the array and unsets this array element.
Alternatively, and maybe even better, you could do getOrder() on the element you want to have before the new element, then increment this order number by X and then loop through the existing form elements and check that the order number doesn't exist yet.
Or you could just use getOrder() on the Element you want to show before and after the new element and make sure you don't use the same order numbers for the new element.
Sorry to be late to the question. What I did was extend Zend_Form and override the _sort() method as follows:
/**
* Sort items according to their order
*
* #return void
*/
protected function _sort()
{
if ($this->_orderUpdated) {
$items = array();
$index = 0;
foreach ($this->_order as $key => $order) {
if (null === $order) {
if (null === ($order = $this->{$key}->getOrder())) {
while (array_search($index, $this->_order, true)) {
++$index;
}
$items[$index][]= $key;
++$index;
} else {
$items[$order][]= $key;
}
} else {
$items[$order][]= $key;
}
}
ksort($items);
$index = 0;
foreach($items as $i=>$item){
foreach($item as $subItem){
$newItems[$index++]=$subItem;
}
}
$items = array_flip($newItems);
asort($items);
$this->_order = $items;
$this->_orderUpdated = false;
}
}
This differs from the original sort method by putting the items in an array based off of their index and then doing a depth-first traversal to flatten the array.
Try this code:
$elements = array();
$elements[] = new Zend_Form_Element_Textarea('address');
......
$elements[] = new Zend_Form_Element_Text('city');
.......
$elements[] = new Zend_Form_Element_Submit($handle);
.....
$this->addElements($elements);
All you need to do is add them in the order you want them to show
what i would do is - use a temp array for that - in that keep the element names in desired order (don't mind the keys). Then use foreach like this:
foreach(array_values($tempArray) as $order => $name) {
$form->$name->setOrder($order+1);
}
Note the array_values - it will return the values as numbered array ;) Not sure if setOrder(0) works - that's why there is +1