How to get the index of the maximum element in an array? - arrays

I want to find the maximum value in an array and print his index.
I wrote this for print the maximum value; it works but I don't know how print the index.
use feature "say";
use List::Util qw(max);
#x=qw(10 -2 -48 -5 7 34 28);
say "valore massimo: ".max #x;

The core List::Util comes with an almighty reduce, which can be used to direclty work out all kinds of results
use warnings;
use strict;
use feature 'say';
use List::Util qw(reduce);
my #x = qw(10 -2 -48 -5 7 34 28);
my $max_idx = reduce { $x[$a] > $x[$b] ? $a : $b } 0..$#x;
say "Maximal value of $x[$max_idx] is at index $max_idx";
Can wrap this in a simple function, to have a clear name for the operation (max_idx or something, and which can return both the element and its index, perhaps only in list context). Libraries for list utilities often merely package reduce expressions into handy functions.
A utility with the above functionality is max_by from List::UtilsBy, as shown in Silvio Mayolo's answer (but we don't have to make an array of indices first).
A nitpick but I 'd like to mention. The list of (obviously) numbers given in the question as qw(10 -2 -48 -5 7 34 28) is a list of strings ("words"), as qw operator builds things.
These are treated as numbers once they are used that way, as normally done by the interpreter, and all is well. However, since they are clearly to be numbers I prefer to introduce them as such
my #x = (10, -2, -48, -5, 7, 34, 28);
A touch more typing but I find that it conveys the intent more clearly. Again, by all means this is of no consequence for most (any?) code.

List::UtilsBy provides the max_by, for getting a maximum according to some other criterion (which could be another list).
use 5.010;
use List::UtilsBy qw/max_by/;
my #x = qw(10 -2 -48 -5 7 34 28);
my #indices = (0..#x-1);
say max_by { $x[$_] } #indices;
Generally, if you're doing nontrivial list manipulation in Perl, I recommend installing List::AllUtils, which is an all-in-one package including List::Util, List::SomeUtils, and List::UtilsBy.

For a small task such as this, you don't really need to use external libraries.
use strict;
use warnings;
use feature 'say';
my #x = (10, -2, -48, -5, 7, 34, 28);
my $max = 0; # first index is the max
for (0 .. $#x) {
if ($x[$_] > $x[$max]) {
$max = $_;
}
}
say "#x";
say "Max number is $x[$max] with index $max";
Output:
10 -2 -48 -5 7 34 28
Max number is 34 with index 5
Just loop over the indexes, check the values and save the index with the highest number.

The task you are doing is absolutely basic and crucial to programming. If you start to learn programming, you should be able to come up with a solution on your own.
Yes, there exists nice modules that make this task a lot more elegant, but if you are learning to programming, you should come up at least with a solution like this, ON YOUR OWN!
printf "%d\n", max_index(10,3,22,5,4,11,33); # prints 6
printf "%s\n", max_index(34,21,100,12,9); # prints 2
sub max_index {
my ( #list ) = #_;
my $max_index = 0;
my $max_value = shift #list;
my $idx = 0;
for my $current ( #list ) {
$idx++;
if ( $current > $max_value ) {
$max_index = $idx;
$max_value = $current;
}
}
return $max_index;
}
Homework:
What happens if you pass no element to the function? What should be returned?
Make it work with an array reference.
Use a classic for-loop for (..., ..., ...) { ... }, and don't use shift.
What happens if you pass strings to it, instead of numbers?
Do you know a solution to problem 4?

In situation if you would prefer to use only Perl due restriction on Perl module installation - to find index of maximum value in an array you could use following algorithm:
assume that first element in the array has $max value
compare following array elements with $max
store index and value for max element if satisfices the condition
use strict;
use warnings;
use feature 'say';
my #arr = qw(10 -2 -48 -5 7 34 28);
my($ind,$max) = find_max(\#arr);
say "arr[$ind] = $max";
sub find_max {
my $arr = shift;
my($i,$max)=(0,$arr->[0]);
for( 1..$#{$arr} ) {
($i,$max) = ($_,$arr->[$_]) if $max < $arr->[$_];
}
return ($i,$max);
}
Output
arr[5] = 34

You can try the following code
use List::Util qw(max);
my #x = qw(10 -2 -48 -5 7 34 28);
my ($index) = ( grep { $x[$_] eq max(#x) } 0..$#x );
print "max ", max(#x), " index $index\n";
Output
max 34 index 5

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]);
}

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 - operations allowed on array while iterating through it

What are the operations allowed on array, while iterating through it?
Is it possible to shift/unshift, pop/push, delete elements without confusing the iterator?
Is that any different for adding/removing key-value pair from hash?
Thank you for your help.
You can assign to existing elements, but should not add or remove them. So no shift, unshift, pop, push, or splice. perlsyn:
If any part of LIST is an array, foreach will get very confused if you add or remove elements within the loop body, for example with splice. So don't do that.
If you are iterating over a hash with each, you should also avoid adding or removing elements, except that you are explicitly allowed to remove the current element. each:
If you add or delete a hash's elements while iterating over it, the effect on the iterator is unspecified; for example, entries may be skipped or duplicated--so don't do that. Exception: It is always safe to delete the item most recently returned by each(), so the following code works properly:
But as it says, the worst that could happen is entries being skipped or duplicated; modifying an array you are looping over, on the other hand, can lead to segfaults.
As ysth has already pointed out, it is unwise to attempt to modify an array while iterating directly on its elements.
However, if one does want to modify an array dependent on the element values, the trick is to do it in reverse index order.
For example, say I have an array of numbers. I would like modifier the array so that every multiple of 4 has a string inserted after it, and every multiple of 5 is removed. I would accomplish that using the following:
use strict;
use warnings;
my #array = ( 1 .. 20 );
for my $i ( reverse 0 .. $#array ) {
# Insert after multiples of 4
if ( ( $array[$i] % 4 ) == 0 ) {
splice #array, $i + 1, 0, "insert";
}
# Remove multiples of 5
if ( ( $array[$i] % 5 ) == 0 ) {
splice #array, $i, 1;
}
}
use Data::Dump;
dd #array;
Outputs:
(
1 .. 4,
"insert",
6,
7,
8,
"insert",
9,
11,
12,
"insert",
13,
14,
16,
"insert",
17,
18,
19,
"insert",
)
Alternatively, if you want to transform an array, it's also possible to use map like so:
my #newarray = map {
( ( ($_) x !!( $_ % 5 ) ), # Remove multiples of 5
( ('insert') x !( $_ % 4 ) ), # Insert After multiples of 4
)
} ( 1 .. 20 );
use Data::Dump;
dd #newarray;

How to compare two arrays in Perl

I have two arrays. I want to find what elements are in the second but not the first, and what elements are in the first but not the second.
Is there any way with out writing multiple loops?
Each array is something like this $array_2d_1
0 ARRAY(0x9929210)
0 ARRAY(0x98df3d8)
0 71
1 22
2 15
3 10
4 51
1 ARRAY(0x9934900)
0 91
1 82
2 28
3 11
4 91
You can use the module List::Compare for this task:
use strict;
use warnings;
use List::Compare;
my #arr1 = qw/5 8 12 42 99 10/;
my #arr2 = qw/10 20 12 18 99 10/;
my $lc = List::Compare->new( \#arr1, \#arr2 );
my #arr1Only = $lc->get_Lonly;
print "\#arr1 only: #arr1Only\n";
my #arr2Only = $lc->get_Ronly;
print "\#arr2 only: #arr2Only\n";
Output:
#arr1 only: 42 5 8
#arr2 only: 18 20
Hope this helps!
You can't avoid looping. This solution has four loops, but they're not nested.
my %a1 = map { $_ => 1 } #a1;
my %a2 = map { $_ => 1 } #a2;
my #missing_from_a1 = grep !$a1{$_}, #a2;
my #missing_from_a2 = grep !$a2{$_}, #a1;
I think you are looking for something like set implementation in perl right. In standard perl there is no set implementation and here is the module in cpan that can achieve what you are trying to solve set. So, we have to find out a way that we can create unique element out of the array, and hash keys in perl is unique. By using hash implementation we can achieved the set implementation. More details you can look up here :
Set Implementation
To take the set difference between two sets, there must be loops involved somewhere. I am assuming you want to avoid writing out the loops every time you need to compute the set difference instead of having an unhealthy aversion to loops.
One simple way would be to abstract away the operation into a subroutine:
#!/usr/bin/env perl
use strict;
use warnings;
main();
sub main {
my #x = (1, 2, 3, 4, 5);
my #y = (3, 5, 7, 8, 9);
for my $sets ( ([\(#x, #y)], [\(#y, #x)]) ) {
print "#{ set_diff( #$sets ) }\n";
}
}
sub set_diff {
my $x = shift;
my %y = map { $_ => undef } #{ $_[0] };
return [ grep not( exists $y{$_} ), #$x ];
}
There are also a number of set implementations on CPAN.

Matching Values in Array with Tolerance

I'm trying to weed out duplicate values in an array, which I'm successfully accomplishing with the "List::MoreUtils uniq/distinct" function.
However, I would also like to count those values that fall within a given tolerance, say +-5, as duplicates as well (I think tolerance is also sometimes referred to as "delta").
For example, if 588 is a value in the array, but so is 589, because the difference falls within the tolerance of 5, 589 gets the boot.
Without some nasty/costly cross-checking of arrays, is there an elegant way to do this?
EDIT: ikegami brought to my attention some ambiguity in my question and I'm having a bit of a hard time wrapping my head around the problem. However, I think I have it worked out.
[500,505,510,515,525,900]
If you try to match the values throughout the entire array, you should get:
[500,510,525,900]
It hits 505, sees it as non-unique, removes it from the array, then sees 510 as newly-unique due to the absence of 505, and so on. This, I imagine is the way I outlined my original question, but on reflection, it seems it's a useless and fairly arbitrary data set.
What I really want is the following match:
[500,900]
It represents a group of numbers that are within 5 of each other, while also spotting the vast variance in the 900 value. This seems to be more useful information than the former and it appears that perreal's answer gets me close. Sorry for the confusion, and many thanks to ikegami as well as perreal for forcing my clarification.
EDIT 2
An even better match would be:
[510,900]
510, being the median of all the sequential +-5 values.
However, I recognize that now we're deviating severely from my original question, so I would be more than happy with an answer to my EDIT 1 clarification.
Isolate the samples that form a chain where each is within the tolerance of the next, then choose one from that group.
sub collapse {
my $tol = shift;
my #collapsed;
while (#_) {
my #group = shift(#_);
while (#_ && $group[-1] + $tol >= $_[0]) {
push #group, shift(#_);
}
push #collapsed, choose_from(#group);
}
return #collapsed;
}
say join ',', collapse(5 => 500,505,510,515,525,900);
So how do you choose? Well, you could return the average.
use List::Util qw( sum );
sub choose_from {
return sum(#_)/#_;
}
# Outputs: 507.5,525,900
Or you could return the median.
use List::Util qw( sum );
sub choose_from {
my $median;
if (#_ % 2 == 0) {
my $avg = sum(#_)/#_;
my $diff0 = abs( $_[ #_/2 - 1 ] - $avg );
my $diff1 = abs( $_[ #_/2 - 0 ] - $avg );
if ($diff0 <= $diff1) {
return $_[ #_/2 - 1 ];
} else {
return $_[ #_/2 - 0 ];
}
} else {
return $_[ #_/2 ];
}
}
# Outputs: 505,525,900
This is a deceptively complex problem, as the data must not only be organized into groups, but also those groups must be combined if a new data point is seen that belongs to more than one of them.
This program seems to do what you need. It keeps a list of arrays #buckets, where each element contains all values seen so far that is within TOLERANCE of one other. This list is scanned to see if each value falls within range of the maximum and minimum values already present. The index of the groups that the value belongs to are stored in memberof, and there will always be zero, one or two entries in this array.
All the groups specified by #memberof are removed from #buckets, combined together with the new data value, sorted, and replaced as a new group in the list.
At the end the #buckets array is converted to a list of median values, sorted and displayed. I have used Data::Dump to show the contents of the groups before they are aggregated to their median values.
To generate your desired output 510, 900 from the list 500, 510, 525, 900 the value for TOLERANCE must be increased so that values that differ by 15 or less are combined.
use strict;
use warnings;
use constant TOLERANCE => 5;
my #data = qw/ 500 505 510 515 525 900 /;
my #buckets;
for my $item (#data) {
my #memberof;
for my $i (0 .. $#buckets) {
if ($item >= $buckets[$i][0] - TOLERANCE and $item <= $buckets[$i][-1] + TOLERANCE) {
push #memberof, $i;
}
}
my #newbucket = ($item);
for my $i (reverse #memberof) {
push #newbucket, #{ splice #buckets, $i, 1 };
}
push #buckets, [ sort { $a <=> $b } #newbucket ];
}
use Data::Dump;
dd #buckets;
#buckets = sort { $a <=> $b } map median(#$_), #buckets;
print join(', ', #buckets), "\n";
sub median {
my $n = #_;
my $i = $n / 2;
if ($n % 2) {
return $_[$i];
}
else {
return ($_[$i-1] + $_[$i]) / 2;
}
}
output
([500, 505, 510, 515], [525], [900])
507.5, 525, 900

Resources