PDA

View Full Version : How do you return a value from a subroutine ?


eristic
01-24-2007, 04:14 PM
I am trying to write a program where a user will enter random numbers, one per line, and then the program will calculate the total, the average, and list the numbers that are greater than the average. I am having a few problems but the main one is how to return the last calculated value from the subroutine.

1. How to get the return value from the subroutine to the rest of the program?

2. I have a question in sub avg. My friend said I had to put the if (@_ == 0). Is that saying if the value entered is numerically 0 or if the # of elements in the array is 0 ?

3. Also regarding sub avg. my $count = @_; is designed to tell me how many elements are in the array, (at least thats what I want it to do. Is it doing that or actually telling me the value of each indice in the array?) Does my $total = &total(@_); run each array element through &total or does it count the number of elements in the array and then run that numerical value through &total ?

4. I used to code for fun in VB and I really enjoyed how you could run the code (or pause it ) line by line to see where it had problems and to see what exactly it was doing if you weren't getting what you wanted. Is there any editor that will do that for perl? I have tried textedit and jedit but have yet to figure something out.


Here is the code:


#!/usr/bin/perl
use warnings;
use strict;
#--------------------------------------------------------------
sub total {
my $n;
foreach (@_) {
$n += $_; #adds $_ (scalar value of $_) to $n
}
$n; #last value returned $n
}
#--------------------------------------------------------------
sub avg { #subroutine to find the average
if (@_ == 0) { return }
my $count = @_;
my $total = &total(@_);
$avg = ($total / $count);
}
#--------------------------------------------------------------
sub above_avg { #subroutine to find #'s > average
my @greater_than_avg;
foreach (@_) {
if ($_ > $avg) { #if scalar var > $avg comparison
push (@greater_than_avg, $_); #push the # onto the array
}
my @sorted = sort { $a <=> $b } @greater_than_avg;
}
}
############################################
############################################

print (
"Please enter a bunch of numbers to calculate the total, the "
"average, and list the numbers that are greater than the average:\n"
);
my $user_total = &total(<STDIN>);
my $user_avg = &avg(<STDIN>);
my @user_above_avg = &above_avg(<STDIN>);
print {
"The total is $user_total\n."
"The average is $user_avg.\n"
"The numbers that are greater than the average are: @user_above_avg.\n"
};

KevinADC
01-24-2007, 05:55 PM
1. How to get the return value from the subroutine to the rest of the program?


You're gonna hate yourself:

return(list);

where 'list' is just about anything from a single scalar to a list of references.

http://perldoc.perl.org/functions/return.html

david_kw
01-24-2007, 06:14 PM
In perl you can use

perl -d myscript.pl

to run in debug mode which allows you to step line by line even if it is kind of clunky interface-wise. I think I recall activeperl for windows sells a debugger that is nicer.

david_kw

KevinADC
01-24-2007, 06:32 PM
2. I have a question in sub avg. My friend said I had to put the if (@_ == 0). Is that saying if the value entered is numerically 0 or if the # of elements in the array is 0 ?

it says the number of elements is zero, in other words, the systm array '@_' has nothing in it.

is designed to tell me how many elements are in the array,

Yes, how many elements (or the length) of the array.

The &total() sub routine is summing up the value of all the elements in the array. If the array were:

20 40 30

it would return 90, not 3.

eristic
01-24-2007, 06:37 PM
You're gonna hate yourself:

return(list);

where 'list' is just about anything from a single scalar to a list of references.

http://perldoc.perl.org/functions/return.html

I followed the link, but found this statement:

(Note that in the absence of an explicit return, a subroutine, eval, or do FILE will automatically return the value of the last expression evaluated.)

So that means that it will automatically return the value of the last expression. How would I get $n in sub total to return its value to sub average ?

eristic
01-24-2007, 07:24 PM
1 if (@_ == 0) { return }
2 my $count = @_;
3 my $total = &total(@_);
4 $avg = ($total / $count);


What I am confused here is line 1 is saying, "If the array has 0 elements (no numbers inputted) then return the following: lines 2, 3, 4.

Shouldn't it say, "if array @_ isn't an empty list (!= 0) then do lines 2, 3, 4. ?

eristic
01-24-2007, 07:27 PM
sub total {
my $n;
foreach (@_) {
$n += $_;
}
$n;
}
#--------------------------------------------------------------
sub avg { #subroutine to find the average
if (@_ == 0) { return }
my $count = @_;
my $total = &total(@_); # --> I want $total = $n. How do I do this w/o rerunning sub &total ?
$avg = ($total / $count);

KevinADC
01-24-2007, 08:21 PM
1 if (@_ == 0) { return }
2 my $count = @_;
3 my $total = &total(@_);
4 $avg = ($total / $count);


What I am confused here is line 1 is saying, "If the array has 0 elements (no numbers inputted) then return the following: lines 2, 3, 4.

Shouldn't it say, "if array @_ isn't an empty list (!= 0) then do lines 2, 3, 4. ?

no, line one is saying if the array is empty to return to wherever it was called from, the rest of the lines in the block are not executed. Note the description of the return() functon is: "get out of a function early".

KevinADC
01-24-2007, 08:24 PM
sub total {
my $n;
foreach (@_) {
$n += $_;
}
return($n);
}
#--------------------------------------------------------------
sub avg { #subroutine to find the average
if (@_ == 0) { return }
my $count = @_;
my $total = &total(@_); # --> I want $total = $n. How do I do this w/o rerunning sub &total ?
$avg = ($total / $count);

FishMonger
01-24-2007, 08:35 PM
Did you try running your code before posting? I'm sure you didn't. This is what it produces.

Variable "$avg" is not imported at C:\testing\eristic.pl line 23.
(Did you mean &avg instead?)
print (...) interpreted as function at C:\testing\eristic.pl line 32.
String found where operator expected at C:\testing\eristic.pl line 34, near ""average, and list the numbers that are greater than the average:\n""
(Missing semicolon on previous line?)
String found where operator expected at C:\testing\eristic.pl line 41, near ""The average is $user_avg.\n""
(Missing semicolon on previous line?)
String found where operator expected at C:\testing\eristic.pl line 42, near ""The numbers that are greater than the average are: @user_above_avg.\n""
(Missing semicolon on previous line?)
Global symbol "$avg" requires explicit package name at C:\testing\eristic.pl line 17.
Global symbol "$avg" requires explicit package name at C:\testing\eristic.pl line 23.
syntax error at C:\testing\eristic.pl line 34, near ""average, and list the numbers that are greater than the average:\n""
syntax error at C:\testing\eristic.pl line 41, near ""The average is $user_avg.\n""
Execution of C:\testing\eristic.pl aborted due to compilation errors.

FishMonger
01-24-2007, 09:11 PM
This is not a hard set of rules, but they are good set of ordered steps you should take when starting out.

1) Load required external modules/libraries.

2) Initialize global variables.

3) Get required input data.

4) Process the input data.

5) Output the results.

Each of those could be broken down into additional steps and include 1 or more subroutines. FYI, passing the STDIN data to a subroutine in the manor that you're doing is rarely if ever a good or proper approach.

eristic
01-24-2007, 10:14 PM
Alright, since it was too messy to look at, I decided to break it into steps. First, I redid a program just to add the total of inputted numbers.


#!/usr/bin/perl
use warnings;
use strict;

print
"Please enter a bunch of numbers to calculate the total, the ",
"average, and list \n the numbers that are greater than the average:\n";

my $user_total = &total(<STDIN>);
print "The total is $user_total\n.";

sub total {
my $n; #declare $n as a counter
foreach (@_) { #foreach array element (@_) of STDIN
$n += $_; #adds $_ (scalar value of $_) to $n
}
$n;
}


It worked! Okay, so now let's try adding up the numbers, then making it find the average using another sub. The part I am stuck on is passing the value of my $avg to $user_avg. I really have no idea how to do that. Here is the code:


#!/usr/bin/perl
use warnings;
use strict;

print
"Please enter a bunch of numbers to calculate the total, the ",
"average, and list the numbers that are greater than the average:\n";

my $user_total = &total(<STDIN>);
my $user_avg = ?????;

print
"The total is $user_total\n.",
"The average is $user_avg.\n";

sub total {
my $n; #declare $n as a counter
foreach (@_) { #foreach array element (@_) of STDIN
$n += $_; #adds $_ (scalar value of $_) to $n
}
$n; #last value returned $n
}

sub avg { #subroutine to find the average
if (@_ == 0) { return }
my $count = @_;
my $total = &total(@_);
my $avg = ($total / $count);
}


Also, I am trying to do the exercise in this book that I am using to self teach myself and I don't have any other method of approach since it tells me to write a subroutine to calculate the total and average that a user inputs.

FishMonger
01-24-2007, 10:42 PM
What book are you using? Is it using code examples like what you're using in this script? If so, through that book away.

Start by reading in (preprocess) the input data into an array OUTSIDE and BEFORE you execute any of those subroutines. Then pass the array to the subs NOT <STDIN>

eristic
01-24-2007, 10:56 PM
What book are you using? Is it using code examples like what you're using in this script? If so, through that book away.

Start by reading in (preprocess) the input data into an array OUTSIDE and BEFORE you execute any of those subroutines. Then pass the array to the subs NOT <STDIN>

I am ready to pull my hair out, this is really frustrating. I tried what you said and this is what I could do (and its not working).


#!/usr/bin/perl
use warnings;
use strict;

print
"Please enter a bunch of numbers to calculate the total, the ",
"average, and list \n the numbers that are greater than the average:\n";

chomp (my @user_nums = (<STDIN>));
print "@user_nums\n"; #checking to see that the array got the user input
my $user_total = &total(@user_nums); #run the numbers through the sub and assign it to a new array
print "total is $user_total";

sub total {
my $total;
foreach my $n (@user_nums) {
$total += $n;
}
$total;
}

eristic
01-24-2007, 11:01 PM
after many iterations its working, but im not sure what I did to get it to work. Going to go over the code before I can move on to make it find the average. I can barley get it to find the total of a set of numbers.

eristic
01-24-2007, 11:17 PM
I'm trying to write it so it calculates the average, but now its giving me errors:

syntax error at average2.pl line 17, near "sub total "
syntax error at average2.pl line 23, near "}"
Execution of average2.pl aborted due to compilation errors.

Not sure what exactly is wrong. sub total is written the same exact way in the previous program and it works just fine. Any ideas ?


#!/usr/bin/perl
use warnings;
use strict;

print
"Please enter a bunch of numbers to calculate the total, the ",
"average, and list the numbers that are greater than the average:\n";

chomp (my @user_nums = (<STDIN>));
print "@user_nums\n"; #checking to see that the array got the user input
my $user_total = &total(@user_nums); #run the numbers through the sub and assign it to a new array
my $user_avg = &avg(@user_nums);

print "The total is $user_total.\n";
print "The average is $user_avg"

sub total { #line 17
my $total;
foreach my $n (@user_nums) {
$total += $n;
}
$total;
} #line 23

sub avg {
if (@_ == 0) { return }
my $count = @user_nums;
my $total = &total(@user_nums);
my $avg = ($total / $count);
}

KevinADC
01-24-2007, 11:20 PM
look at the line before line 17, there is something missing on the end of it....

eristic
01-24-2007, 11:22 PM
arrrrrrrrrr

eristic
01-24-2007, 11:24 PM
look at the line before line 17, there is something missing on the end of it....

the program now calculates the total and the average. QUick question... when I had set $user_total and $user_avg as arrays (on accident) the program still gave the correct scalar calculations. What's up with that ?

eristic
01-24-2007, 11:27 PM
What book are you using? Is it using code examples like what you're using in this script? If so, through that book away.


The code I was getting examples from is "Leraning Perl" or as I have learned from reading online, also referred to as "The Llama."

KevinADC
01-25-2007, 12:12 AM
good job, you might find this interesting:

#!/usr/bin/perl
use warnings;
use strict;

print
"Please enter a bunch of numbers to calculate the total, the ",
"average, and list the numbers that are greater than the average:\n";

chomp (my @user_nums = (<STDIN>));
print "\n";
print "The total is ", total(@user_nums), "\n";
print "The average is ", avg(@user_nums);

sub total {
my $total;
$total += $_ for (@_);
return($total);
}

sub avg {
return 0 unless @_;
my $avg = (total(@_) / @_);
return($avg);
}

eristic
01-25-2007, 03:01 AM
good job, you might find this interesting:


Wow! Kevin, that's really good to see. The code is much cleaner and easier on the eyes. One key thing that I noticed is that you don't have to declare the values such as $user_total and $user_avg. I like how you can call them (or rather the value that they expressed )by simply putting total(@user_nums). I did not know.

Also, no "&" which the book said is only needed sometimes, but better safe than sorry until you learn all the rules of it :D I am still on that process. Lastly, I like how you used the default variables. It is less declaration and again very clean and easy. I still have apprehensions about using @_ & $_ because it is still not clear on how the two relate and what exactly they represent in different statements of the subs.

One more thing. I have only seen that "for" command used once in the book, and it seems to make things much simpler. I'm going to look up that command.

FishMonger
01-25-2007, 03:28 AM
One "gatcha" to be aware of when passing vars to/from subroutines is that you could be modifing a global var my mistake. What I'm refering to is called "pass by value" and "pass by reference". With the method that you and Kevin have used, the subroutines are working directly with and can easily modify the global array (pass by reference). In most cases, you'll want to pass a copy of the array (pass by value).

To pass a copy, you'd do it this way:
print "The average is ", avg(@user_nums);

sub avg {
my @nums = @_;
return 0 unless @nums;
This is not the cleanest of examples, but it should help to show what I mean.
#!/usr/bin/perl
use warnings;
use strict;

print
"Please enter a bunch of numbers to calculate the total, the ",
"average, and list the numbers that are greater than the average:\n";

chomp (my @user_nums = (<STDIN>));
print "original user input @user_nums\n\n";

print "returned from multiply sub: ", multiply(@user_nums), "\n";
print "user input array outside/after mutiply sub: @user_nums\n\n";

print "returned from pluss sub: ", pluss(@user_nums), $/;
print "user input array outside/after pluss sub: @user_nums\n\n";

sub multiply {
print "global var passed to multiply sub: @_\n";
$_ *= 2 for (@_);
print "global var modified inside multiply sub: @_\n";
return "@_";
}

sub pluss {
my @nums = @_;
print "global var passed/copied into add sub: @nums\n";
$_ += 2 for (@nums);
print "local var inside pluss sub: @nums\n";
return "@nums";
}

eristic
01-25-2007, 04:18 AM
Fishmonger: I think I get what you are saying. I had to look at the output and compare it to the code for a few minutes to see it. Pass by reference can open a can of worms because we are changing the actual values of our initial array (inputted by user), so its chaning a global variable from a private subroutine.

A pass by value approach will hold our initial array constant and unchanged, to throw through the subroutines to return what we want, without affecting the users initial array, being the global variable outside the scope of the subroutine.

At least I think that's what you are saying. I can see the importance of passing by value if you ever wanted to access the initial array. Did I interpret that right?

FishMonger
01-25-2007, 05:06 AM
Yep, you got it. The Llama book doesn't go into the details becasue it's targeted for the beginner. I'd suggest picking up the Camel book (Programming Perl) which is a "must have" reference book, and the Perl Cookbook.

KevinADC
01-25-2007, 07:02 AM
FishMonger has made a very important point. For this simple script passing the @_ around is no problem, but as you get more into perl, you will need to know the difference between doing things directly with the data in @_ or using a copy of the data, which is safer and a good habit to get into. It's also a good habit to get into using the return() function.

If you read postings on forums or get involved on forums you will learn lots of stuff the books leave out.