Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

John Guttag's book - 2nd exercise. My attempt in Perl.

by pritesh_ugrankar (Monk)
on May 20, 2017 at 20:22 UTC ( [id://1190754]=perlquestion: print w/replies, xml ) Need Help??

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

Hi Monks,

I came across John Guttag's book on computation - "Introduction to Computation and Programming Using Python". Note that I love Perl, but found this book to be very interesting. I tried the second finger exercise and though, initially it appeared trivial to solve, it took me a real long time to figure it out.

Here's the exercise: Write a program that examines three variables— x , y , and z — and prints the largest odd number among them. If none of them are odd, it should print a message to that effect.

Points to note:- This is to be tackled using only if/else loop and comparisons, because that is what the author has covered till that part.

Here's my attempt in Perl. I tried other ways, but most of them would not print the expected results if either of the numbers were negative, or two of the three numbers were even.

use strict; use warnings; my ($x, $y, $z) = (-11,-13,4); if ($x % 2 == 0 and $y % 2 == 0 and $z % 2 == 0) { print "All are even numbers\n"; } elsif ($x % 2 == 0 && $y % 2 == 0) { print "$z is the biggest odd number\n"; } elsif ($y % 2 == 0 && $z % 2 == 0) { print "$x is the biggest odd number\n"; } elsif ($x % 2 == 0 && $z % 2 == 0) { print "$y is the biggest odd number\n"; } elsif ($x % 2 == 0 && $y % 2 != 0 && $z % 2 != 0) { if ($y > $z) { print "$y is the greatest odd number\n"; } else { print "$z is the biggest odd number\n"; } } elsif ($y % 2 == 0 && $x % 2 != 0 && $z % 2 != 0) { if ($x > $z) { print "$x is the greatest odd number\n"; } else { print "$z is the biggest odd number\n"; } } elsif ($z % 2 == 0 && $x % 2 != 0 && $z % 2 != 0) { if ($x > $y) { print "$x is the greatest odd number\n"; } else { print "$y is the biggest odd number\n"; } } else { if ($x > $y && $x > $z) { print "$x is the biggest odd number\n"; } elsif ($y > $z) { print "$y is the biggest odd number\n"; } else { print "$z is the biggest odd number\n"; } }

Update: As pointed out by Choroba, I did a typo at line 26. Sorry for the typo. I've fixed it below.

use strict; use warnings; my ($x, $y, $z) = (-11,-13,-17); if ($x % 2 == 0 and $y % 2 == 0 and $z % 2 == 0) { print "All are even numbers\n"; } elsif ($x % 2 == 0 && $y % 2 == 0) { print "$z is the biggest odd number\n"; } elsif ($y % 2 == 0 && $z % 2 == 0) { print "$x is the biggest odd number\n"; } elsif ($x % 2 == 0 && $z % 2 == 0) { print "$y is the biggest odd number\n"; } elsif ($x % 2 == 0 && $y % 2 != 0 && $z % 2 != 0) { if ($y > $z) { print "$y is the greatest odd number\n"; } else { print "$z is the biggest odd number\n"; } } elsif ($y % 2 == 0 && $x % 2 != 0 && $z % 2 != 0) { if ($x > $z) { print "$x is the greatest odd number\n"; } else { print "$z is the biggest odd number\n"; } } elsif ($z % 2 == 0 && $x % 2 != 0 && $z % 2 != 0) { if ($x > $z) { print "$x is the greatest odd number\n"; } else { print "$z is the biggest odd number\n"; } } else { if ($x > $y && $x > $z) { print "$x is the biggest odd number\n"; } elsif ($y > $z) { print "$y is the biggest odd number\n"; } else { print "$z is the biggest odd number\n"; } }

While this works fine for all the test cases I tried, but I will be thankful if you could please let me know a better way of writing this. Point to note is, it should only use if/else/elsif loops and / or comparison operators.

Replies are listed 'Best First'.
Re: John Guttag's book - 2nd exercise. My attempt in Perl.
by choroba (Cardinal) on May 20, 2017 at 20:32 UTC
    > While this works fine for all the test cases I tried

    Does it? I'm getting

    4 is the biggest odd number

    for the very sample you gave. Maybe the second $z on line 26 should be $y ?

    Update: Here's my solution. It first sorts the numbers, then finds the first odd one starting from the greatest one.

    sub cmp3 { my ($x, $y, $z) = @_; ($y, $x) = ($x, $y) if $y < $x; ($z, $y) = ($y, $z) if $z < $y; ($y, $x) = ($x, $y) if $y < $x; if (1 == $z % 2) { return "$z is the biggest odd number\n" } elsif (1 == $y % 2) { return "$y is the biggest odd number\n" } elsif (1 == $x % 2) { return "$x is the biggest odd number\n" } else { return "All are even numbers\n" } }

    Tested against yours with

    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
Re: John Guttag's book - 2nd exercise. My attempt in Perl.
by duelafn (Parson) on May 20, 2017 at 21:44 UTC

    This will do it a bit more nicely (fewer branches) without modifying the variables (and using only if/elsif as requested):

    use 5.010; my ($x, $y, $z) = (-11,-13,4); if ($x % 2 and ($x > $y or not $y % 2) and ($x > $z or not $z % 2)) { say "$x is the largest odd"; } elsif ($y % 2 and ($y > $z or not $z % 2)) { say "$y is the largest odd"; } elsif ($z % 2) { say "$z is the largest odd"; } else { say "All numbers are even!"; }

    Update: If I were doing this for real, I'd do:

    my @nums = (-11, -13, 4); my @candidates = sort { $a <=> $b } grep $_ % 2, @nums; if (@candidates) { say $candidates[-1]; } else { say "They're all even!"; }

    Good Day,
        Dean

Re: John Guttag's book - 2nd exercise. My attempt in Perl.
by kcott (Archbishop) on May 21, 2017 at 06:29 UTC

    G'day pritesh_ugrankar,

    Here's one way to do it:

    #!/usr/bin/env perl use strict; use warnings; use constant { INPUT => 0, EXPECT => 1, NO_ODDS => 'no odds found' }; use Test::More; my @tests = ( [[qw{-11 -13 4}], -11], [[qw{-12 -14 4}], NO_ODDS], [[qw{0 0 0}], NO_ODDS], [[qw{1 1 1}], 1], [[qw{1 0 1}], 1], [[qw{0 0 1}], 1], [[qw{2 3 2}], 3], [[qw{2 -3 2}], -3], [[qw{-2 -3 -4}], -3], [[qw{-1 -3 -5}], -1], [[qw{3 5 7}], 7], [[qw{7 5 3}], 7], [[qw{5 7 3}], 7], ); plan tests => scalar @tests; for my $test (@tests) { my ($x, $y, $z) = @{$test->[INPUT]}; my $u = ($x, $y)[$x % 2 ? ($y % 2 ? $x < $y : 0) : 1]; my $v = ($u, $z)[$u % 2 ? ($z % 2 ? $u < $z : 0) : 1]; ok((NO_ODDS, $v)[$v % 2] eq $test->[EXPECT]); }

    See Test::More if you're not familiar with that module. It's very useful in situations like this where you want to test a solution against a variety of input data.

    All those tests passed:

    1..13 ok 1 ... 2 to 12 all 'ok' also ... ok 13

    As you can hopefully see, it's very easy to add more test data by simply adding more elements to @tests.

    — Ken

Re: John Guttag's book - 2nd exercise. My attempt in Perl.
by shmem (Chancellor) on May 20, 2017 at 20:55 UTC
    Point to note is, it should only use if/else/elsif loops and / or comparison operators.

    So no modulo operator, no division operator, no arithmetic operators, no bitwise foo... how would you determine oddness only with comparison? You would need an array which holds all odd values of |N. My computer can't.

    perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
      I guess the OP meant to say that you should not use things like sort, map, grep or List::Util. The code in the OP is using the modulo operator, so we should assume this is allowed.

      Hi,

      Modulo Operator can be used as I've used in my code. Sorry I should've stated that earlier.

      Thinkpad T430 with Ubuntu 16.04.2 running perl 5.24.1 thanks to plenv!!
Re: John Guttag's book - 2nd exercise. My attempt in Perl.
by KurtZ (Friar) on May 20, 2017 at 20:38 UTC
    Well a generic solution is to loop over all variables and check against a $max which is initially undef and grows whenever a bigger odd variable is encountered.

    You may want to store them in a hash to keep the names associated (are hashes allowed? )

      Coded as such:

      use strict; use warnings; # Initialize my ($x, $y, $z) = (-11,-13,4); my $maxodd; # Intentionally left as undef # Check $x (though $maxodd will be undef, write it consistently in cas +e code is revectored later) if ($x % 2) { if ( (!defined $maxodd) || ( (defined $maxodd) && ($x > $maxodd) ) + ) { $maxodd = $x; } } # Check $y if ($y % 2) { if ( (!defined $maxodd) || ( (defined $maxodd) && ($y > $maxodd) ) + ) { $maxodd = $y; } } # Check $z if ($z % 2) { if ( (!defined $maxodd) || ( (defined $maxodd) && ($z > $maxodd) ) + ) { $maxodd = $z; } } # Report results if (!defined $maxodd) { print "All are even numbers\n"; } else { print "$maxodd is the greatest odd number\n"; } # Fini exit;

      This screams for the use of a subroutine, though I suspect the author isn't at that point in the book yet.

Re: John Guttag's book - 2nd exercise. My attempt in Perl.
by shmem (Chancellor) on May 21, 2017 at 08:29 UTC
    This is to be tackled using only if/else loop and comparisons, because that is what the author has covered till that part.

    Well, the modulo operator should be permitted. We could shortcut with next and unless, but if it hasn't been covered, an inner if() block will do:

    use strict; use warnings; my ($x, $y, $z) = (-11,-13,4); my $max; for($x,$y,$z) { # next unless $_ % 2; if ($_ % 2) { $max = $_ unless defined $max; $max = $_ if $max < $_; } } print "max odd: ", $max || "not found","\n";

    update: darn, I wrote a for() loop, which ostensibly hasn't been covered up to that point. But why hasn't it?

    OTOH, you wrote if/else loop - anyways, to avoid the for() loop, let's unroll it:

    use strict; use warnings; my ($x, $y, $z) = (-11,-13,4); my $max = 0; if($x % 2) { if ( ! $max ) { $max = $x; } elsif ($max < $x ) { $max = $x; } } if($y % 2) { if ( ! $max ) { $max = $y; } elsif ($max < $y ) { $max = $y; } } if($z % 2) { if ( ! $max ) { $max = $z; } elsif ($max < $z ) { $max = $z; } } print "max odd: ", $max || "not found","\n";

    This would be a good example for introducing loop control, leading to the loop layed out above.

    update: made each 2nd if block into elsif, as per choroba's comment below.

    perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
      As $x can never be less than $x, you can turn each second if in each block into elsif (provided it has been covered).
      ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,

        Sure, but the point was to unroll the loop. Ah, I see. Right. Fixed.

        perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
Re: John Guttag's book - 2nd exercise. My attempt in Perl.
by Laurent_R (Canon) on May 20, 2017 at 21:48 UTC
    Maybe this subroutine:
    use strict; use warnings; my @array = (-11,-13,4, 22, 17); print find_max_odd(@array), "\n"; sub find_max_odd { my $max; while ($max = shift) { last if $max % 2; } return "Not found" unless @array; while (my $current = shift) { next unless $current % 2; if ($current > $max) { $max = $current; } } return $max; }
    Update: As pointed out by AnoMonk below, these while loops have a bug when one of the array item is 0.I suggest a better for loop in another post below.
      you can avoid the first loop if you change

      if ($current > $max) {

      to

      if ( not defined $max or $current > $max) {

      $max would simply stay undef if all values were even.


      no it's more complicated ... wait
      This should do:
      use strict; use warnings; my @array = ( -11, -13, 4, 22, 17); print "Result: ",find_max_odd(@array) // "undefined", "\n"; sub find_max_odd { my $max; for (@_) { next unless $_ % 2; unless ( defined $max ) { $max = $_; next; } $max = $_ if $_ > $max; } return $max; }

      I was confused, but becaus eof short-circuiting of the or the first idea should do too:
      use strict; use warnings; my @array = (-11,-13,4, 22, 17); print find_max_odd(@array), "\n"; sub find_max_odd { my $max; while (my $current = shift) { next unless $current % 2; if (not defined $max or $current > $max) { $max = $current; } } return $max // "undefined"; }
        Yeah, KurtZ, I definitely agree with you that it can be made simpler (++), and I most likely wouldn't code this the way I have shown.

        I only suggested a solution that used only very basic Perl knowledge since it was what the OP was asking for. Also, having two loops did not bother me too much, since they're browsing the array items only once anyway.

      Should while (my $current = shift) be  while ( defined( my $current = shift ) ) ? Otherwise it seems to fail if @array contains 0.
        Yes, Anonymous Monk, you're correct, this while loop has a bug when one of the values in the array is 0.

        IMHO, this array traversal should really be made with a for loop, as it is usually better to use a for loop to iterate over an array, but the OP did not specify very clearly what was authorized and what not.

        This is what it might look like with a for loop.

        use strict; use warnings; my @array = (-11,-13,4, 22, 17); print find_max_odd(@array), "\n"; sub find_max_odd { my $max; for my $num (@_) { next unless $num % 2; $max = $num unless defined $max; $max = $num if $num > $max; } return defined $max ? $max : "No odd number found\n"; # this co +uld be replaced with if conditional statements if needed }
Re: John Guttag's book - 2nd exercise. My attempt in Perl.
by johngg (Canon) on May 20, 2017 at 22:30 UTC

    You could use grep and the core List::Util::max() to get what you want.

    johngg@shiraz:~/perl/Monks > perl -Mstrict -Mwarnings -MList::Util=max + -E ' my @tests = ( [ 17, -3, 12 ], [ 2, 4, 6 ], [ -7, -4, -11 ], [ -5, 5, 16 ], ); foreach my $test ( @tests ) { my $maxOdd = max grep { $_ % 2 } @$test; say $maxOdd ? $maxOdd : q{No result}; }' 17 No result -7 5

    I hope this is helpful.

    Cheers,

    JohnGG

Re: John Guttag's book - 2nd exercise. My attempt in Perl.
by tybalt89 (Monsignor) on May 21, 2017 at 17:11 UTC

    Here's a solution using only if/else, <, and %. No ||, &&, and, or, =, or anything else allowed.
    It's sort of silly for something that is a one-liner in normal perl.

    ( note: the comments following open braces show which variables are still valid at this point.)

    #!/usr/bin/perl # http://perlmonks.org/?node_id=1190754 use strict; use warnings; my ($x, $y, $z) = (-11,-13,4); if( $x % 2 ) { # xyz if( $y % 2 ) { # xyz if( $x < $y ) { # yz if( $z % 2 ) { # yz if( $y < $z ) { # z print "z of $z is the biggest odd number\n"; } else { # y print "y of $y is the biggest odd number\n"; } } else { # y print "y of $y is the biggest odd number\n"; } } else { # xz if( $z % 2 ) { # xz if( $x < $z ) { # z print "z of $z is the biggest odd number\n"; } else { # x print "x of $x is the biggest odd number\n"; } } else { # x print "x of $x is the biggest odd number\n"; } } } else { # xz if( $z % 2 ) { # xz if( $x < $z ) { # z print "z of $z is the biggest odd number\n"; } else { # x print "x of $x is the biggest odd number\n"; } } else { # x print "x of $x is the biggest odd number\n"; } } } else { # yz if( $y % 2 ) { # yz if( $z % 2 ) { # yz if( $y < $z ) { # z print "z of $z is the biggest odd number\n"; } else { # y print "y of $y is the biggest odd number\n"; } } else { # y print "y of $y is the biggest odd number\n"; } } else { # z if( $z % 2 ) { # z print "z of $z is the biggest odd number\n"; } else { # print "All are even numbers\n"; } } }
      Now write one that checks 26 vars: $a .. $z

      Also, nested ifs count as ORs and ANDs :D

        No, they don't.

      I wish I could downvote this 100 times.

        I'm just following the posted rules :)

        Let's see your solution following the posted rules...

Re: John Guttag's book - 2nd exercise. My attempt in Perl.
by BillKSmith (Monsignor) on May 22, 2017 at 20:49 UTC
    I am probably to late to be of any help, but I can add one significant detail. I find the logic more intuitive if we initialize the result with a large negative number. You can get the largest possible number from POSIX, but with only a small loss in generality, we can choose any number that is larger than any we expect in our input.
    se strict; use warnings; #use POSIX qw(DBL_MAX); my $x = $ARGV[0] // -11; my $y = $ARGV[1] // -13; my $z = $ARGV[2] // -17; #my $MAX_NEG = -DBL_MAX; my $MAX_NEG = -99999; # Better to use DBL_MAX from POSIX my $max = $MAX_NEG; if ( $x % 2) { $max = $x ; } if ( $y % 2 and $y > $max ) { $max = $y ; } if ( $z % 2 and $z > $max ) { $max = $z ; } die "There are no odd values.\n" if ($max == $MAX_NEG) ; print "the largest odd number is $max.\n";

    Note: I took the freedom to use the disallowed operator (//) to override the default values of $x, $y, and $z with values from the command line. I do not consider this part of the solution.

    Bill
Re: John Guttag's book - 2nd exercise. My attempt in Perl.
by Anonymous Monk on May 22, 2017 at 22:23 UTC
    Not 3 vars but any cli args:
    my $largest_odd; for (@ARGV) { $largest_odd = $_ if $_ % 2 && $_ > $largest_odd; } print $largest_odd ? $largest_odd : 'nothing odd', $/;
Re: John Guttag's book - 2nd exercise. My attempt in Perl.
by pritesh_ugrankar (Monk) on May 25, 2017 at 19:18 UTC

    Respected Monks,

    Thank you for all your answers.

    After seeing all the replies here, I too had the urge to try it out my way, so I threw away all the restrictions that the example had put and tried it my way, so that it will work for any numbers, positive or negative, decimal/hex/octal/binary, and came up with this.

    use strict; use warnings; my @numbers = (10, 110, 24, 30, -17,-9,12,14,-011,-0xF,0b111,0); sub biggest_odd { my @odd_only = grep {$_ % 2 != 0} @_; if (!@odd_only) { print "No odd numbers at all\n"; return; } my $biggest_odd_num = shift @odd_only; foreach my $odd_num(@odd_only) { if ($odd_num > $biggest_odd_num) { $biggest_odd_num = $odd_num; } } print "Biggest odd is $biggest_odd_num\n"; } &biggest_odd(@numbers);

    And here is the output:

    C:perlscripts>perl jg_find_biggest_odd.pl Biggest odd is 7

    Like I said earlier, I really love Perl, but I find the John Guttag book so interesting that I've started learning Python just so that I can understand the concepts given in the book. I don't know how much successful I will be learning two languages simultaneously, especially given the fact that I love the Perl way of solving problems...

    Thinkpad T430 with Ubuntu 16.04.2 running perl 5.24.1 thanks to plenv!!

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others cooling their heels in the Monastery: (5)
As of 2024-04-18 19:42 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found