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

Hey, folks .. Trying to work my way through 'Learning Perl' and I'm on Chapter 4 (Subroutines). I think I've got 90% of a working program but I can't get my head around the last part and it seems to do with using information generated outside a subroutine inside the subroutine and vice versa.

Please help. Thanks in advance. This is my code

#!/usr/bin/perl use strict; use warnings; sub total { my $sum; foreach (@_) { $sum += $_; } $sum; } print "Give me some numbers to add together:\n"; my @num_list = <STDIN>; my $num_list_total = total(@num_list); my $size = @num_list; my @average = $num_list_total / $size; print "The total of the numbers in your list is $num_list_total.\n"; print "The average of the numbers is @average.\n"; sub above_average { # my $average = average(@_); @average = average(@_); my @list; foreach my $element (@_) { if ($element > @average) { push @list, $element; } } @list; } my $num_above_average = above_average(@num_list); print "The following numbers are above the group average: $num_above_a +verage\n";

Replies are listed 'Best First'.
Re: Pure variable confusion!
by aaron_baugher (Curate) on Apr 22, 2015 at 00:42 UTC
    my @average = $num_list_total / $size; print "The average of the numbers is @average.\n";

    This is a bug waiting to happen. $average is a scalar variable containing a single value. @average is an array variable containing a list of values. You're getting away with it here because the first line puts the result of the division in the first element of @average, and then the print statement prints out the whole array, which happens to have only that one element. But if you assigned or used the value a bit differently, it could easily break. Use scalar variables for single values.

    @average = average(@_);

    Same problem here, assigning what's likely a single value to an array (which will be a problem in a few lines). You're also calling a subroutine named average() which doesn't exist in your script, which means your script doesn't run at all. Have you actually tried running it as it exists here?

    Anyway, that line assigns the result of the average() call to the first element of @average. Which means that this line doesn't do what you want:

        if ($element > @average) {

    Since this evaluates @average in scalar context, it uses the number of elements in @average, not the value(s) stored in it. Since you (presumably) stored a single value in it, this has the effect of:

        if($element > 1){

    So again, store your single values in scalar variables. That's a bigger problem than whatever confusion you may be causing by localizing the same variables inside and outside your subroutine.

    Aaron B.
    Available for small or large Perl jobs and *nix system administration; see my home node.

Re: Pure variable confusion!
by Anonymous Monk on Apr 21, 2015 at 23:28 UTC

    You don't appear to have a sub average in your program, where does it come from?

    Also , your numbers probably have extra characters you haven't accounted for, you can figure it out if you examine your data by Data::Dump::dd()umpering to visualize your data (lesson courtesy of Basic debugging checklist and brian's Guide to Solving Any Perl Problem )

      I did the following the calculate the average:

      my @average = $num_list_total / $size;

      As far as my input goes I'm entering the numbers 1 3 6 7 8 (hitting enter after each) and the CTRL-D

        @average is an array; don't you mean $average? And you have the opposite problem at the end of your program. Your function above_average returns an array, but you have the value assigned to a scalar. I suggest you take the time to learn how to use the debugger. There are links to tutorials in the Tutorials section here. One of the great things about the debugger is you can check the contents of variables to make sure they are what you expect, and you can experiment with alternate code to find the solution.

        Dum Spiro Spero
        Then what is this
        # my $average = average(@_); @average = average(@_);
        Where is sub average?
Re: Pure variable confusion!
by ww (Archbishop) on Apr 22, 2015 at 16:12 UTC

    See also answers at passing paramters to a sub, esp GrandFather's and those others dealing with PASS BY REFERENCE as opposed to PASS BY VALUE. And not just by the way, you would have found many nodes offering answers by using Super Search.

    And here's a tutorial-like example of not only "using information generated outside a subroutine inside the subroutine and vice versa" but also of some other aspects of an approach to other matters.

    #!/usr/bin/perl use strict; use warnings; use 5.016; # if 5.016 is available, allows "say;" otherwise # delete this and replace the "say"s with "print"+ +newlines # if 5.016 or > avail, uncomment the 'say "\t DEBUG +..."' lines # to see values they present. # 1124160 my ( $input, $num, @nums, $num_total ); START: say "Type a number and <ENTER> or type 'q' when finished:"; chomp ($input = <STDIN>); if ( ($input eq 'Q') || ($input eq 'q') ) { goto END_INPUT; } elsif ($input =~ /\d+/){ push @nums, $input; goto START; } else { print "Input invalid; type a number or 'q': \n"; } END_INPUT: $num_total = total(@nums); say "\t DEBUG: \$num_total: $num_total"; my $quant_entries = scalar(@nums); # say "\t DEBUG\$quant_entries: $quant_entries"; my $average = ($num_total / $quant_entries); # say "\t DEBUG \$average: $average"; print "The total of the numbers in your list is $num_total.\n"; print "The average of the numbers is $average .\n"; my @list = above_average(\@nums, \$average); # see PASS BY REFERENC +E; otherwise array # FLATTENs to multiple + values before # it's (use-able or us +ed) in the sub print "The following numbers are above the group average: "; for $_ ( @list ) { print "$_, "; } ####### subs sub total { my $sum; foreach (@_) { # @_ contains the 2 arguments you passed +from at Ln 31 $sum += $_; # compare to above_average(), which uses +a different method # of passing in data } return $sum; } #### end sub total() ##### sub above_average { my ( @avg, @nums, $num ); # These are NEW vars - DISTINCT from t +hose in the mainline my ($avg, @list ); # These are ALSO new and also localize +d to this sub # shift takes one arg (var) off @_ whi +ch is the ( default) # container for values passed to a sub + my @localNums = @{$_[0]}; # Deref first arg (a ref) from Ln 38 # say "\t DEBUG at Ln 59: \@localNums: @localNums"; $avg = ${$_[1]}; # Deref the second var passed from Ln +38 # say "\t DEBUG at Ln 61: \$avg: $avg"; for (@localNums) { if ( $_ > $avg ) { push \@list, $_; } } return @list; } #######end sub above_average() #########

    Much of this is perhaps redundant at this late date and, in any case, note that (TIMTOWTDI) your preferences may not match my style.


    Come, let us reason together: Spirit of the Monastery