Finding index of the lowest value in array - arrays

I have 3 arrays #energy, #es_energy and #hb_energy each of which have been indexed with the same name term [$k].
I want to find the lowest value in #energy and then using that index value look for the corresponding values in the other arrays.
Currently I am using my $n = nmin_by { $energy[$_] } 0 .. $#energy;
And then $n is used to output from the other arrays. However, I don't want to use nmin_by as it requires an extra library to download for the software package I am using (loads of admin issues).
Any suggestions?

Use List::Util::reduce
use warnings;
use strict;
use feature 'say';
use List::Util qw(reduce);
my #ary = (12, 3, 1, 23);
my $min_idx = reduce { $ary[$a] < $ary[$b] ? $a : $b } 0..$#ary;
say $min_idx;
Put this in a sub so that the implementation is out of sight while the name clarifies the purpose
use Carp;
sub get_min_idx {
my $ra = shift;
croak "Sub expects array reference" if ref $ra ne 'ARRAY';
return reduce { $ra->[$a] < $ra->[$b] ? $a : $b } 0..$#$ra;
}
my $min_idx = get_min_idx(\#ary);
Tuck it away in a module and you can also change how it works with minimal intrusion.
The error message can be elaborated (tell to user what has been passed, for instance) and checks added; for one, given the numeric < comparison the sub needs an array with only numbers.
Syntax clafirication: the index of the last element of an arrayref $rary is $#$rary (while the index of the last element of an array #ary is $#ary).
Pick your subroutine name carefully; having a good name helps a lot.
Thanks to Borodin for commenting on the need for this.

Related

How to Iterate through multiple Perl arrays

I am hoping to make a loop that allows me to use less lines of code to make changes to a settings file with Perl. Currently my code reads an XML file and locates a settings ID and replaces the setting value in that ID with a new one. The current request involves a lot of changes to the settings file and the code is very long. I have set my values in an array and my settings ID's in an array. Like this:
#GreetGoalDP1 = (3, 5, 7, 10);
#GreetSIDSunDP1 = ('//xsd:Settings/xsd:Setting[#SID="7012"]/xsd:Value',
'//xsd:Settings/xsd:Setting[#SID="7013"]/xsd:Value',
'//xsd:Settings/xsd:Setting[#SID="7014"]/xsd:Value',
'//xsd:Settings/xsd:Setting[#SID="7015"]/xsd:Value');
and run the following.
my($matchSunDP1G1) = $xpc->findnodes($GreetSIDSunDP1[0]);
$matchSunDP1G1->removeChildNodes();
$matchSunDP1G1->appendText($GreetGoalDP1[0]);
#GreetB
my($matchSunDP1G2) = $xpc->findnodes($GreetSIDSunDP1[1]);
$matchSunDP1G2->removeChildNodes();
$matchSunDP1G2->appendText($GreetGoalDP1[1]);
#GreetC
my($matchSunDP1G3) = $xpc->findnodes($GreetSIDSunDP1[2]);
$matchSunDP1G3->removeChildNodes();
$matchSunDP1G3->appendText($GreetGoalDP1[2]);
#GreetD
my($matchSunDP1G4) = $xpc->findnodes($GreetSIDSunDP1[3]);
$matchSunDP1G4->removeChildNodes();
$matchSunDP1G4->appendText($GreetGoalDP1[3]);
I would like to run these changes through a loop just using the array [0] - [3] until completed as I have to do this same set of 4 multiple times. I am not too familiar with looping arrays. Is this something I can do in Perl? If so, what would be the most efficient way to do so?
A simple take
use warnings;
use strict;
...
for my $i (0..$#GreetGoalDP1) {
my ($matchSunDP1G) = $xpc->findnodes( $GreetSIDSunDP1[$i] );
$matchSunDP1G->removeChildNodes();
$matchSunDP1G->appendText( $GreetGoalDP1[$i] );
}
I take it that you don't need all those individual $matchSunDP1G1 etc. It's assumed that the two arays always have the same length, and their elements are needed in pairs at same indices.
The syntax $#aryname is for the last index in the array #aryname, and .. is the range operator, so 0 .. $#GreetGoalDP1 for your example is the list 0,1,2,3.
Then there are libraries that help with use of multiple arrays in parallel, that can be particularly useful when things get messier or more complicated. An example of using an iterator
use List::MoreUtils qw(each_array);
my $it = each_array #GreetSIDSunDP1, #GreetGoalDP1;
while ( my ($sidsun, $goal) = $it->() ) {
my ($matchSunDP1G) = $xpc->findnodes($sidsun);
$matchSunDP1G -> removeChildNodes();
$matchSunDP1G -> appendText( $goal );
}
If the lists are uneven in size the iterator keeps going through the length of the longer one. After the shorter one gets exhausted its would-be value is undef.
Following code sample demonstrates how you could use %hash for alternation you try to achieve.
my %hash = (
3 => '//xsd:Settings/xsd:Setting[#SID="7012"]/xsd:Value',
5 => '//xsd:Settings/xsd:Setting[#SID="7013"]/xsd:Value',
7 => '//xsd:Settings/xsd:Setting[#SID="7014"]/xsd:Value',
10 => '//xsd:Settings/xsd:Setting[#SID="7015"]/xsd:Value')
);
while( my($k,$v) = each %hash ) {
my $match = $xpc->findnodes($v);
$match->removeChildNodes();
$match->appendText($k);
}
Reference: hash, hash operations
Yet Another Way, using zip from the core List::Util module:
#!/usr/bin/env perl
use warnings;
use strict;
use List::Util qw/zip/;
...;
my #GreetGoalDP1 = (3, 5, 7, 10);
my #GreetSIDSunDP1 = ('//xsd:Settings/xsd:Setting[#SID="7012"]/xsd:Value',
'//xsd:Settings/xsd:Setting[#SID="7013"]/xsd:Value',
'//xsd:Settings/xsd:Setting[#SID="7014"]/xsd:Value',
'//xsd:Settings/xsd:Setting[#SID="7015"]/xsd:Value');
foreach my $pair (zip \#GreetSIDSunDP1, \#GreetGoalDP1) {
my ($matchSunDP1G1) = $xpc->findnodes($pair->[0]);
$matchSunDP1G1->removeChildNodes();
$matchSunDP1G1->appendText($pair->[1]);
}

Why does a reference to an array change to a hash reference when passed to a subroutine

I came across this issue in a tool I'm attempting to fix and don't understand what causes the array reference change to a hash reference. It only occurs when the array ref is listed after the hash ref in the parameters, which makes me think it's due the way the parameters are received.
I don't have the code from the tool but the below code recreates what was happening.
use strict;
use warnings;
sub PassArrRef{
my #array = [0,1,2,3,4];
my %hash = {0,'a',1,'b',2,'c'};
RecieveHashRef(\%hash, \#array)
}
sub RecieveHashRef{
my %hash = %{$_[0]};
my $arrayref = shift;
print $arrayref;
}
PassArrRef();
This code outputs a Hash reference.
It is because in ReceiveHashRef you are directly referencing the first positional parameter, $_[0] and then on the next line calling shift which takes the first element of the positional parameters. The same thing.
The typical way to consume the subroutine arguments would be:
my($hashref, $arrayref) = #_;
Totally fixed, your code should look like this. The diagnostics pragma will give you much more expansive explanations of errors.
use strict;
use warnings;
use diagnostics;
sub PassArrRef{
my #array = (0,1,2,3,4);
my %hash = (0,'a',1,'b',2,'c');
ReceiveHashRef(\%hash, \#array)
}
sub ReceiveHashRef{
my($hashref, $arrayref) = #_;
print "hash ref: $hashref, array ref: $arrayref\n";
}
PassArrRef();
And make liberal use of https://perldoc.perl.org/!

Assign multiple local vars to array entries in perl

In Perl, I've always been confused about how to cleanly assign multiple local variables from array entries.
I use the following syntax in subs all the time, so I'm somewhat familiar with it:
my ($var1, $var2) = #_
but other variations of this confuse me. For instance, I have the following code that works:
for my $ctr (0 .. $#matchingLines) {
my $lineNo = $matchingLines[$ctr][0];
my $text = $matchingLines[$ctr][1];
Where "#matchingLines" is an array of two-element arrays.
I wish I could convert the last two lines to the obvious:
my ($lineNo, $text) = $matchingLines[$ctr];
That of course does not work. I've tried numerous variations, but I can't find anything that works.
Just dereference the array ref:
my ( $lineNo, $text ) = #{ $matchingLines[$ctr] };
Check out Perl Data Structures Cookbook for additional examples.
It sounds like you have an array of arrays. this means that the inner arrays will be array references. If you want to allocate them to vars then you need to derference them.
use strict;
use warnings;
my #matchingLines = (['y','z'],['a','b']);
for my $ctr (0 .. $#matchingLines) {
my ($lineNo, $text) = #{$matchingLines[$ctr]};
print "#Array index: $ctr - lineno=$lineNo - text=$text\n"
}
this produces the output
#Array index: 0 - lineno=y - text=z
#Array index: 1 - lineno=a - text=b

Count Perl array size

I'm trying to print out the size of my array. I've followed a few other questions like this one on Stack Overflow. However, I never get the result I want.
All I wish for in this example is for the value of 3 to be printed as I have three indexes. All I get, from both print methods is 0.
my #arr;
$arr{1} = 1;
$arr{2} = 2;
$arr{3} = 3;
my $size = #arr;
print $size; # Prints 0
print scalar #arr; # Prints 0
What am I doing wrong, and how do I get the total size of an array when declared and populated this way?
First off:
my #arr;
$arr{1} = 1;
$arr{2} = 2;
$arr{3} = 3;
is nonsense. {} is for hash keys, so you are referring to %arr not #arr. use strict; and use warnings; would have told you this, and is just one tiny fragment of why they're considered mandatory.
To count the elements in an array, merely access it in a scalar context.
print scalar #arr;
if ( $num_elements < #arr ) { do_something(); }
But you would need to change your thing to
my #arr;
$arr[1] = 1;
$arr[2] = 2;
$arr[3] = 3;
And note - the first element of your array $arr[0] would be undefined.
$VAR1 = [
undef,
1,
2,
3
];
As a result, you would get a result of 4. To get the desired 'count of elements' you would need to filter the undefined items, with something like grep:
print scalar grep {defined} #arr;
This will take #arr filter it with grep (returning 3 elements) and then take the scalar value - count of elements, in this case 3.
But normally - you wouldn't do this. It's only necessary because you're trying to insert values into specific 'slots' in your array.
What you would do more commonly, is use either a direct assignment:
my #arr = ( 1, 2, 3 );
Or:
push ( #arr, 1 );
push ( #arr, 2 );
push ( #arr, 3 );
Which inserts the values at the end of the array. You would - if explicitly iterating - go from 0..$#arr but you rarely need to do this when you can do:
foreach my $element ( #arr ) {
print $element,"\n";
}
Or you can do it with a hash:
my %arr;
$arr{1} = 1;
$arr{2} = 2;
$arr{3} = 3;
This turns your array into a set of (unordered) key-value pairs, which you can access with keys %arr and do exactly the same:
print scalar keys %arr;
if ( $elements < keys %arr ) { do_something(); }
In this latter case, your hash will be:
$VAR1 = {
'1' => 1,
'3' => 3,
'2' => 2
};
I would suggest this is bad practice - if you have ordered values, the tool for the job is the array. If you have 'key' values, a hash is probably the tool for the job still - such as a 'request ID' or similar. You can typically tell the difference by looking at how you access the data, and whether there are any gaps (including from zero).
So to answer your question as asked:
my $size = #arr;
print $size; # prints 0
print scalar #arr; # prints 0
These don't work, because you never insert any values into #arr. But you do have a hash called %arr which you created implicitly. (And again - use strict; and use warnings; would have told you this).
You are initializing a hash, not an array.
To get the "size" of your hash you can write.
my $size = keys %arr;
I just thought there should be an illustration of your code run with USUW (use strict/use warnings) and what it adds to the troubleshooting process:
use strict;
use warnings;
my #arr;
...
And when you run it:
Global symbol "%arr" requires explicit package name (did you forget to declare "my %arr"?) at - line 9.
Global symbol "%arr" requires explicit package name (did you forget to declare "my %arr"?) at - line 10.
Global symbol "%arr" requires explicit package name (did you forget to declare "my %arr"?) at - line 11.
Execution of - aborted due to compilation errors.
So USUW.
You may be thinking that you are instantiating an element of #arr when you are typing in the following code:
$arr{1} = 1;
However, you are instantiating a hash doing that. This tells me that you are not using strict or you would have an error. Instead, change to brackets, like this:
$arr[1] = 1;

Perl subroutines and arrays

I'm just starting out Perl (about 15 minutes ago) using a tutorial online. I made a small subroutine to test a few Perl features and would like to know if it is possible to determine at runtime if parameters that were passed to the sub' call are arrays or scalars. Let's use the sub I wrote as an example:
#!/usr/bin/perl
sub somme_prod {
if (scalar(#_) > 1) {
$facteur = shift(#_);
foreach my $nb (#_) {
$resultat += $nb
}
return ($resultat * $facteur);
}
else {
return "ERREUR";
}
}
print somme_prod(2, 2, 3, 7);
This is a basic sum-product subroutine which does exactly what its name says. Now, would it be possible to modify this subroutine to allow for a mix of arrays and scalars like this ?
somme_prod(2, (2,3), 7);
somme_prod(2, (2,3,7));
#...
Also, any comment on the style of Perl coding demonstrated here is much welcome. I have a background of amateur C++ coding so I may not be thinking in Perl.
Edit: I'm so sorry. I actually tried it after posting and it seems that Perl does process my sub as I want it to. Now I guess my question would be more "how does Perl know how to process this" ?
Edited code for a more Perl-ish version.
Yes; in Perl you can create references to arrays (or hashes, or anything else) to stuff several values into a single parameter.
For example:
somme_prod(2, [2, 3], 7);
...would resolve to:
sub somme_prod {
foreach my $arg (#_) {
if (ref($arg) eq 'ARRAY') {
my #values = #$arg; # dereference, e.g. [2, 3] -> (2, 3)
. . .
} else {
# single value, e.g. "2" or "7"
}
}
}
You can read the page perldoc perlref to learn all about references.
Perl handles lists and arrays differently, and a useful document for you to read is What is the difference between a list and an array?
Perl will always flatten nested lists (and so arrays within lists) so
my #data1 = (2, (2, 3), 7);
or
my #data2 = (2, 3);
my #data1 = (2, #data2, 7);
is equivalent to
my #data1 = (2, 2, 3, 7);
As Kevin says, if you want nested arrays you have to place an array reference in the place where the sublist appears. Because a reference is a scalar it won't get expanded.
Your subroutine is fine, but using some de-facto standards would help others to follow your program. Firstly the convention is that a subroutine will return undef if there is an error, so that you can write
sous_routine($p1, $p2) or die "Erreur";
In this case the possibility that zero is a valid result spoils this, but it is still best to stick to the rules. A plain return without a parameter indicates an error
A little bit of tidying up and using unless and if as statement modifiers gives this
sub somme_prod {
return unless #_ > 1;
my $facteur = shift;
my $somme = 0;
$somme += $_ for #_;
return $somme * $facteur;
}
print somme_prod(2, 2, 3, 7);
You've known Perl for 15 minutes? Forget about references for now.
Basically, everything passed to a subroutine is an array. In fact, it's stored in an array called #_.
# /usr/bin/env perl
use strict; #ALWAYS USE!
use warnings; #ALWAYS USE!
my #array = qw(member1 member2 member3 member4);
foo(#array, 'scalar', 'scalar', 'scalar');
sub foo {
print "My input is " . join (":", #_) . "\n";
This will print:
my input is member1:member2:member3:member4:scalar:scalar:scalar
There is no way to tell which entries are from an array and which are from a scalar. As far as your subroutine is concerned, they're all members of the array #_.
By the way, Perl comes with a command called perldoc. When someone says to see perlref, you can type in perldoc perlref at the command line and see the documentation. You can also go to the site http://perldoc.perl.org which will also contain the same information found in the perldoc command.
Now about references....
A data element of a Perl array or the value of a hash can only contain a single value. That could be a string, it could be a real number, it could be an integer, and it could be a reference to another Perl data structure. That's where all the fun and money is.
For example, the same subroutine foo above could have taken this information:
foo(\#array, 'scalar', 'scalar', 'scalar'); #Note the backslash!
In this case, you're not passing in the values of #array into foo. Instead, a reference to the array is passed as the first data element of #_. If you attempted to print out $_[0], you'd get something like ARRAY:6E43832 which says the data element is an array and where it's located in memory.
Now, you can use the ref function to see whether an piece of data is a reference and the type of reference it is:
sub foo {
foreach my $item (#_) {
if (ref $item eq 'ARRAY') {
print "This element is a reference to an array\n";
}
elsif (ref $item eq 'HASH') {
print "This element is a reference to a hash\n";
}
elsif (ref $item) { #Mysterious Moe Reference
print "This element is a reference to a " . lc (ref $item) . "\n";
}
else {
print "This element is a scalar and it's value is '$item'\n";
}
}
}
Of course, your reference to an array might be an array that contains references to hashes that contain references to arrays and so on. There's a module that comes with Perl called Data::Dumper (you can use perldoc to see information about it) that will print out the entire data structure.
This is how object orient Perl works, so it's really quite common to have references to other Perl data structures embedded in a piece of Perl data.
Right now, just get use to basic Perl and how it works. Then, start looking at the various tutorials about Perl references in Perldoc.

Resources