Join the Stack Overflow Community
Stack Overflow is a community of 6.6 million programmers, just like you, helping each other.
Join them; it only takes a minute:
Sign up

I'm trying to write a script to find the greatest number among 3 numbers, and pass them through command line arguments

I just want to know what is the wrong things I wrote in this code, thanks for any help.

sub maximum($) {
    ($num1) = @_ ; 
    ($num2) = @_ ; 
    ($num2) = @_ ; 

    $max = $num1;

    if ($num2 > $num1) {
        $max = $num2;
    }    
    elsif ($num3 > $max ) {
        $max = $num3;
    }
}

$n1 = $ARGV[0];
chomp($n1);
$n2 = $ARGV[1];
chomp($n2);
$n3 = $ARGV[2];
chomp($n3);

maximum($n1, $n2, $n3);
share|improve this question
2  
List::Util is core Perl and contains a max() function that does what your maximum() function is supposed to do. – DavidO Jan 2 at 18:40
2  
@DavidO Oops, didn't see your comment. Just posted it as an answer. – Chankey Pathak Jan 2 at 18:47
    
Add a use strict; and use warnings; to the top of every Perl file until you know exactly why each is recommended. – Brad Gilbert Jan 3 at 20:50
    
@alex213: you can accept one of the answers by clicking on the grey checkmark below its score. – chqrlie Jan 12 at 5:45

You maximum function has flaws:

  • You only try the third number if the first is greater than the second.
  • You do not retrieve the arguments correctly; $num1 and $num2 both get the first and $num3 is never set.
  • You never return the $max value.
  • You don't print the result.
  • You don't scope your variables.
  • There's no reason to chomp arguments.

Here is some code with five different approaches for function argument retrieval:

sub max1 {
    my $num1 = $_[0];
    my $num2 = $_[1];
    my $num3 = $_[2];
    my $max = $num1;
    $max = $num2 if $num2 > $max;
    $max = $num3 if $num3 > $max;
    return $max;
}

sub max2 {
    my $num1 = shift;
    my $num2 = shift;
    my $num3 = shift;
    my $max = $num1;
    $max = $num2 if $num2 > $max;
    $max = $num3 if $num3 > $max;
    return $max;
}

sub max3 {
    my ($num1, $num2, $num3) = @_;
    my $max = $num1;
    $max = $num2 if $num2 > $max;
    $max = $num3 if $num3 > $max;
    return $max;
}

# Works for any number of arguments.
sub max4 {
    my $max = shift;
    foreach my $foo (@_) {
        $max = $foo if $max < $foo;
    }
    return $max;
}

# Works for any number of arguments.
use List::Util qw( max );

my $n1 = $ARGV[0];
my $n2 = $ARGV[1];
my $n3 = $ARGV[2];

printf "%d\n", max1($n1, $n2, $n3);
printf "%d\n", max2($n1, $n2, $n3);
printf "%d\n", max3($n1, $n2, $n3);
printf "%d\n", max4($n1, $n2, $n3);
printf "%d\n", max($n1, $n2, $n3);

# Even simpler:
printf "%d\n", max1 @ARGV;
printf "%d\n", max2 @ARGV;
printf "%d\n", max3 @ARGV;
printf "%d\n", max4 @ARGV;
printf "%d\n", max @ARGV;
share|improve this answer

The logic error in your original code is that there are times where the third number never gets tested. The elsif should be an if.

You are also relying on implicit return values, which will surprise you. For example, if you were to fall through to the elsif conditional, and its conditional expression was false, the maximum function will return with a false value (an empty string), which is not at all what you would actually want. This is because if Perl hits no return statement in your subroutine, it returns the value of the last expression evaluated. In this case, that last expression very well could be a conditional evaluation. The other surprising thing is that it will work in cases where the conditional evaluates to true, because in such cases the last expression evaluated is the assignment to $max, so that's what gets returned.

A more general purpose solution would take any quantity of numbers and find the max. It turns out that by using a loop we can eliminate chained if(){...} elsif()... statements, while becoming more flexible. Here is a reasonable general-purpose approach that should work for any size list of numbers:

sub maximum {
    my $max = shift;
    foreach my $num (@_) {
        $max = $num if $num > $max;
    }
    return $max;
}

print maximum(1,5,3), "\n"; # Prints 5.

But there's an even better approach:

use List::Util 'max';

print max(1,5,3), "\n"; # Also prints 5.

The List::Util module comes with Perl, so you don't have to install anything you shouldn't already have.

share|improve this answer

In addition to chqrlie's answer I would like to introduce you to Perl Modules which make the job easy. For example in your case you could use List::Util module. It's a core module therefore you don't have to put extra efforts to install it.

Example:

#!/usr/bin/perl
use strict;
use warnings;
use List::Util qw( max );
my $maximum = max @ARGV;
print "Maximum: $maximum\n";

Demo

share|improve this answer

You should indeed use List::Util::max. However, just for the sake of variety, here is a compact way of getting the maximum of three arguments:

sub max_of_3 {
    my $x = $_[ $_[1] > $_[0] ];
    return $_[2] > $x ? $_[2] : $x;
}

Here is some test code along with benchmarks:

#!/usr/bin/env perl

use strict;
use warnings;

use Algorithm::Combinatorics qw( permutations );
use Dumbbench;
use List::Util qw( max );
use Test::More;

sub dummy { }

sub max_of_3 {
    my $x = $_[ $_[1] > $_[0] ];
    return $_[2] > $x ? $_[2] : $x;
}

sub classic {
    my $num1 = $_[0];
    my $num2 = $_[1];
    my $num3 = $_[2];
    my $max = $num1;
    $max = $num2 if ($num2 > $max);
    $max = $num3 if ($num3 > $max);
    return $max;
}

for my $case ( permutations([5, 6, 7])) {
    is max_of_3(@$case), 7, "max of [@$case] is 7";
}

done_testing;

my $bench = Dumbbench->new;

$bench->add_instances(
    Dumbbench::Instance::PerlSub->new(
        name => 'Dummy',
        code => sub { dummy(5, 6, 7) },
    ),
    Dumbbench::Instance::PerlSub->new(
        name => 'compact',
        code => sub { max_of_3(5, 6, 7) },
    ),
    Dumbbench::Instance::PerlSub->new(
        name => 'List::Util::max',
        code => sub { max(5, 6, 7) },
    ),
    Dumbbench::Instance::PerlSub->new(
        name => 'classic',
        code => sub { classic(5, 6, 7) },
    ),
);

$bench->run;
$bench->report;

Benchmark output:

Dummy: Ran 21 iterations (0 outliers).
Dummy: Rounded run time per iteration: 1.4729e-007 +/- 4.1e-010 (0.3%)
compact: Ran 27 iterations (7 outliers).
compact: Rounded run time per iteration: 3.8513e-007 +/- 8.4e-010 (0.2%)
List::Util::max: Ran 37 iterations (8 outliers).
List::Util::max: Rounded run time per iteration: 1.28262e-007 +/- 2.3e-011 (0.0%)
classic: Ran 27 iterations (7 outliers).
classic: Rounded run time per iteration: 5.81187e-007 +/- 9.2e-011 (0.0%)

The morals of the story: 1) Use well-established libraries instead of rolling your own. Here List::Util::max is faster than invoking a subroutine that does nothing because it is implemented in XS; 2) Prefer clarity over compactness unless you can come up with a really, really good reason. In most cases, even a 35% improvement in speed is not worth it, especially since using List::Util::max is simultaneously clearer, more compact and it performs better than both hand-rolled alternatives. You also don't have to write new code to handle N arguments.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.