Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

Passing Variables

by catfish1116 (Beadle)
on Feb 25, 2021 at 21:01 UTC ( #11128799=perlquestion: print w/replies, xml ) Need Help??

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

I am running v5.12 and am trying to pass multiple args to a subroutine. Is this possible in v5.12? Here is my code

################################################### # # # 2/24/21 # # Program takes finds the average in an # # array and lists out the numbers that were # # above the average found. # # # ################################################### my @numbers = qw(4 12 18 21 35); my $average = &find_average(@numbers); print "The average found for this list is: $average \n " ; my @high_avg = &above_average($average, @numbers); print "The numbers that were found above average are: @high_avg \n" +; sub find_average { my ($sum, $number_count) ; foreach (@_) { $sum += $_; $number_count += 1; } my $avg = $sum / $number_count ; } sub above_average { my $average_num; my @final_list; foreach $_ (@_) { if ($_ > $average_num) { push @final_list, $_ } } }

Here is the error message I am getting

Use of uninitialized value $average_num in numeric gt (>) at ./Chapter +4_Ex3.pl line 36.

TIA The Catfish

Replies are listed 'Best First'.
Re: Passing Variables
by jdporter (Canon) on Feb 25, 2021 at 21:10 UTC

    You need to initialize $average_num from the argument list:

    my $average_num = shift;
Re: Passing Variables
by toolic (Bishop) on Feb 25, 2021 at 21:12 UTC
    The problem is in your above_average sub. Perl flattens everything passed into one list. So, your @_ also contains the $average passed in. You should first remove $average using shift. Also, you should explicitly return your @final_list array. Change it to:
    sub above_average { my $average_num = shift; my @final_list; foreach $_ (@_) { if ($_ > $average_num) { push @final_list, $_ } } return @final_list; }
    For more details, see perlsub
Re: Passing Variables
by LanX (Cardinal) on Feb 25, 2021 at 22:15 UTC
Re: Passing Variables
by GrandFather (Saint) on Feb 28, 2021 at 23:50 UTC

    Lets refactor that to be a little more Perl idiomatic:

    ################################################### # # # 2/24/21 # # Program takes finds the average in an # # array and lists out the numbers that were # # above the average found. # # # ################################################### use strict; use warnings; my @numbers = qw(4 12 18 21 35); my $average = find_average(@numbers); my @high_avg = above_average($average, @numbers); print <<STR; The average found for this list is: $average The numbers that were found above average are: @high_avg STR sub find_average { die "find_average expects to be passed a list of numbers" if !@_; my $sum = 0; $sum += $_ for @_; return $sum / @_; } sub above_average { my ($average_num, @values) = @_; return grep {$_ > $average_num} @values; }

    Prints:

    The average found for this list is: 18 The numbers that were found above average are: 21 35

    First note the use of strictures (use strict; use warnings;). Always use strictures.

    Then notice that subs are called without & in modern Perl. There are nasty subtle traps using & to call subs, so don't do that.

    Use grep to select elements from a list.

    Use here docs for outputting large blocks of text.

    Use Statement Modifiers for very simple conditional statements or loops.

    Return simple results directly rather than assigning them to an intermediate variable, but always use return to make it clear what is returned from the sub.

    Assigning a list or array to a scalar (something that has $ at the front) assigns the count of elements so you can use that directly instead of counting the elements as they are processed.

    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
Re: Passing Variables
by Marshall (Canon) on Feb 25, 2021 at 22:32 UTC
    I refactored you code. Please study and ask questions:
    ################################################### # # # 2/24/21 # # Program takes finds the average in an # # array and lists out the numbers that were # # above the average found. # # # ################################################### ### Limitations - added 3/1/2021 ### This code will throw a fatal "Illegal division by zero" ### error if @numbers doesn't contain at least one element. my @numbers = qw(4 12 18 21 35); my $average = find_average(@numbers); print "The average found for this list is: $average \n" ; my @above_avg = above_average($average, @numbers); print "The numbers that were found above average are: @above_avg \n +"; sub find_average { my @nums = @_; my $sum; foreach my $num (@nums) { $sum += $num; } my $avg = $sum / @nums; return $avg; } sub above_average { my ($average_num, @nums) = @_; my @final_list; foreach my $num (@nums) { if ($num > $average_num) { push @final_list, $num } } return @final_list; } __END__ PRINTS: The average found for this list is: 18 The numbers that were found above average are: 21 35

      sub find_average needs to:

      1. initialize my $sum = 0;
      2. handle the arg list being empty. You probably don't want to throw a divide-by-zero exception.

      I suppose I'd write sub above_average like this:

      sub above_average { my $average_num = shift; grep { $_ > $average_num } @_ }
        Your comments are appropriate, thanks!
        My goal was to refactor the OP's code in a straight-forward way, with the intent of being instructive - there are of course imperfections .
        When coding this myself, I would be thinking about functions in List::Util and passing references to array instead of an array itself. But that is not what I thought my job here was. My intent was to present easy to understand code. But even so, I figure that the OP will have some questions about it.
        "initialize my $sum = 0;"

        An interesting variation, that is probably premature optimization in simple cases, is to:

        sub average { my $sum = shift @_; ...;

        For algorithms such as moving averages or integrators initializing the accumulator with the first sample value can be essential to avoiding large glitches at the start of the result data.

        Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond

      I get most of the revisions, except this line my ($average_num, @nums) = @_ Does this line of code say that the first arg is the number (average) and the second argument is the array? And that we are loading both of them into the default, (@_) array ? Marshall, thanks for your assistance with this. The Catfish

        You are close. All arguments to a subroutine are passed as a single ordered array. That array is accessed in the subroutine by @_.
         my ($average_num, @nums) = @_; means take the first element of @_ and make a copy of it into the subroutine's variable $average_num, then copy all of the elements that are "left over" to the subroutine's array of @nums.

        my ($average_num, $first_num, @rest_of_nums) = @_; is possible. But you can't have my ($average_num, @nums, $last_num) = @_; because @nums will consume all entries that are left in @_ and $last_num will be undefined.

        The @_ array is very special and some tricks can be played in certain circumstances. None of which apply in 99.9% of Perl code.

        Consider the following:

        use strict; use warnings; my @numbers = qw(4 12 18 21 35); my $average = find_average(\@numbers); print "The average found for this list is: $average \n" ; ## find_average will cause a fatal error if ## there are no numbers in the array. sub find_average { # subroutine gets a sinle value which is a reference # to an array, not an arry itself. This is much, much # faster if say the array has say 1,000 elements. my $num_array_ref = shift; # could be: my ($num_array_ref) = @_; # The shift operation is very slightly faster if # only one value is involved. my $sum; # no need to set $sum=0; # but I wouldn't quibble if you did that. foreach my $num (@$num_array_ref) { $sum += $num; } my $avg = $sum / @$num_array_ref; return $avg; } __END__ The average found for this list is: 18
        Update: I know that some Monks would say that the "return $avg" statement is extraneous because by default, Perl will return the value of the last statement in the subroutine. I don't want to relitigate that except to say that an explicit return statement is in my opinion good practice.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://11128799]
Approved by toolic
Front-paged by toolic
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others romping around the Monastery: (3)
As of 2021-04-15 01:27 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?