Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Hi monks,

A very Happy New Year to all.

Here's a question to kick off the new year: I've two arrays containing an equal number of digits. I want to multiply the digits from array1 with the corresponding digits from array2. With warnings enabled, I get the "Use of uninitialized value in multiplication (*)" warning. I try to fix that in the following code.

my $sum = 0; for (0..@weights) { $weights[$_] = $weights[$_] || 0; $digits[$_] = $digits[$_] || 0; $sum += $weights[$_] * $digits[$_]; }
That suppresses the warning but should I be worry about it in the first place? Is there a better way to do the above?

Thank you so much in advance :)

Replies are listed 'Best First'.
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)

      Change to: for ( 0..$#weights )

      Alternatively do away with all that tedious messing around with indices completely :-)

      use List::Util qw( sum ); use List::MoreUtils qw( pairwise ); my $sum = sum pairwise { $a * $b } @weights, @digits;
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.
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";

    Prasad

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;
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;
      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.
        no warnings 'uninitialized';

        Makeshifts last the longest.

        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?

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!

      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"; }


      holli, /regexed monk/

        In principle, that is the approach, but I’d execute it a little differently since we’re not writing Pascal:

        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.