Re: A better way than ||
by fishbot_v2 (Chaplain) on Dec 31, 2005 at 15:43 UTC
|
So, part (all?) of the problem here is that you have an off-by-one error in your array traversal: @weights in scalar context will give you the number of elements in the array, not the last index. Thus you are always falling off the end of your weights array. Change to:
for ( 0..$#weights ) { # ...
You've said that the two arrays have the same number of elements. If they are all defined, always, then you have solved the problem.
If any of them might be undefined (ie. the array is sparse) then you have a different issue, which you probably want to solve the way you supressed the warning above. (or with $weights[$_] ||= 0)
| [reply] [d/l] [select] |
|
|
use List::Util qw( sum );
use List::MoreUtils qw( pairwise );
my $sum = sum pairwise { $a * $b } @weights, @digits;
| [reply] [d/l] [select] |
Re: A better way than ||
by Celada (Monk) on Dec 31, 2005 at 15:50 UTC
|
I guess you must be in a part of the world that is already in 2006, so Happy New Year to you as well!
As you said, the two arrays have the same number of elements, so if you are just accessing all of the elements of each array in turn, there is no reason why you should be accessing uninitialized elements. The problem is this:
for (0..@weights)
That for loop will count one past the last element in the array, because the scalar value of @weights is the number of elements in the array, not the index of the last element. You should change this to:
for (0..$#weights)
To be really, really sure, you could do this:
for ($[..$#weights)
To go from the first element to the last element, but in sane code, $[ is always supposed to be 0 anyway, so I wouldn't bother with it. | [reply] [d/l] [select] |
Re: A better way than ||
by prasadbabu (Prior) on Dec 31, 2005 at 15:59 UTC
|
Is there a better way to do the above?
If i understood your question correctly the following will help you? There is a module to accomplish this job List::MoreUtils
use strict;
use warnings;
use List::MoreUtils qw(pairwise);
@weights = (1 .. 5);
@digits = (11 .. 15);
@sum = pairwise { $a * $b } @weights, @digits; # returns 12, 14, 16,
+ 18, 20
print "@sum";
| [reply] [d/l] |
Re: A better way than ||
by davidrw (Prior) on Dec 31, 2005 at 17:27 UTC
|
aside from the off-by-one issue already mentioned, if there are still undef's you can do:
my $sum = 0;
$sum += ($weights[$_]||0) * ($digits[$_]||0) for 0 .. @#weights;
Also note that, in general, $x = $x || 0; can be stated as $x ||= 0;
Another way:
my $sum = 0;
for (0..@#weights) {
next unless $weights[$_] && $digits[$_];
$sum += $weights[$_] * $digits[$_];
}
# or
for (0..@#weights) {
$sum += $weights[$_] * $digits[$_] if $weights[$_] && $digits[$_
+];
}
# or yet another:
$sum += $weights[$_] * $digits[$_] for grep $weights[$_] && $digits
+[$_], 0..@#weights;
| [reply] [d/l] [select] |
Re: A better way than ||
by TedPride (Priest) on Dec 31, 2005 at 18:37 UTC
|
Why must programmers always choose the most complicated solution? Just turn off warnings for the block:
use strict;
use warnings;
my @weights = (1,2,7,8);
my @digits = (2,3,4);
my $sum;
for (0..$#weights) {
no warnings;
$sum += $weights[$_] * $digits[$_];
}
print $sum;
| [reply] [d/l] |
|
|
In general it's better to work around a specific warning that you've verified is harmless by a code change; that way, you don't turn off warnings other than the specific one you are concerned about. In the example you give, you are also turning off warnings about non-numeric data in $sum, @weights, and @digits, which is a different concern than undefs.
| [reply] |
|
|
no warnings 'uninitialized';
Makeshifts last the longest. | [reply] [d/l] |
|
|
Yes, but note the line "I've two arrays containing an equal number of digits". If you already know that a certain type of error will not occur, why bother doing extra programming to prevent it? Computers don't just get up in the morning and decide to give you letters instead of numbers as an April Fool's Day joke.
This reminds me of an old game called Robot Odyssey that Dad and I used to play on our Apple IIE. There's a certain puzzle where your robot has to travel through a dark room from one side to the other, dodging all the force fields scattered around. Your character isn't allowed to go into the room himself, which is why you have to send the robot. Dad spent days carefully programming the robot to make the entire trek on its own. I looked over the problem for about five minutes, then put my character inside the robot and manually wired it for each separate movement. Perhaps my way wasn't the most elegant solution, but who cares as long as I made it to the objective?
| [reply] |
|
|
Re: A better way than ||
by Anonymous Monk on Jan 01, 2006 at 07:18 UTC
|
Thank you so much, everybody :)
My apologies for not making some things clearer in my haste to simplify the question.
In my code above, the array @weights is pre-defined so I should not have that line to assign each token "0" because it can't possibly be undefined - this enlightenment came after reading the wonderful feedback that I've got from your replies.
The other array @digits is the result of an input from a web form. Specifically, it's an identification number of the shape 9525423E. @digits is gotten using a split:
# $input is received from a web submission,
# assigned a value here for simplification
$input = '9525423E';
@digits = split //, $input;
my $letter = uc(pop @digits);
So @digits should contain 7 numeric characters after the pop. But users may enter an invalid identification number, e.g one that contains only 6 digits instead of 7. So the extra code is need to assign each individual element of @digits a value to prevent the "Use of uninitialized value..." warning.
Cheers! | [reply] [d/l] |
|
|
it's an identification number of the shape 9525423E. @digits is gotten using a split:
I think in this case you're better of using a regex, so you can verify the input split more conveniently:
my $input = '9525429E';
if ( $input =~ /^([0-9]{7})([A-Z])$/ )
{
my @digits = split //, $1;
print "@digits, $2";
}
else
{
print "invalid input";
}
| [reply] [d/l] |
|
|
my ( $digits, $letter ) = $input =~ m{ ^ (\d{7}) ([A-Z]) $ }x;
if( not defined $digits ) {
print "Invalid input: $input\n";
return;
# or last, or exit, depending on what sort of block we're in
}
# rest of code such as split //, $digits follows here
That avoids imposing another level of indentation upon the code in the normal flow of execution, and it keeps the condition check and its error handling close together.
Makeshifts last the longest. | [reply] [d/l] |