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.
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,
| [reply] [d/l] [select] |
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!";
}
| [reply] [d/l] [select] |
Re: John Guttag's book - 2nd exercise. My attempt in Perl.
by kcott (Archbishop) on May 21, 2017 at 06:29 UTC
|
#!/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.
| [reply] [d/l] [select] |
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'
| [reply] [d/l] |
|
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.
| [reply] [d/l] [select] |
|
| [reply] |
|
|
| [reply] [d/l] |
Re: John Guttag's book - 2nd exercise. My attempt in Perl.
by KurtZ (Friar) on May 20, 2017 at 20:38 UTC
|
| [reply] |
|
| [reply] [d/l] |
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'
| [reply] [d/l] [select] |
|
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,
| [reply] [d/l] [select] |
|
| [reply] |
Re: John Guttag's book - 2nd exercise. My attempt in Perl.
by Laurent_R (Canon) on May 20, 2017 at 21:48 UTC
|
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. | [reply] [d/l] |
|
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";
}
| [reply] [d/l] [select] |
|
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.
| [reply] |
|
Should while (my $current = shift) be while ( defined( my $current = shift ) ) ? Otherwise it seems to fail if @array contains 0.
| [reply] [d/l] [select] |
|
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
}
| [reply] [d/l] [select] |
Re: John Guttag's book - 2nd exercise. My attempt in Perl.
by johngg (Canon) on May 20, 2017 at 22:30 UTC
|
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.
| [reply] [d/l] [select] |
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";
}
}
}
| [reply] [d/l] |
|
| [reply] |
|
| [reply] |
|
|
I wish I could downvote this 100 times.
| [reply] |
|
| [reply] |
|
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.
| [reply] [d/l] |
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', $/;
| [reply] [d/l] |
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!!
| [reply] [d/l] [select] |
|
|