Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses

General style advice requested

by iangibson (Scribe)
on Oct 22, 2010 at 02:08 UTC ( #866695=perlquestion: print w/replies, xml ) Need Help??
iangibson has asked for the wisdom of the Perl Monks concerning the following question:

I am a newcomer to Perl (and to programming in general; I just finished the Llama book) and someone recommended that I work through the problems on the website by way of practice. This is my attempt at the first problem. It produces the correct output, so I'd just like to know if there's any style issues or other bad practices in my code that I should know about:

#!/usr/bin/perl use warnings; use strict; use 5.010; # The 3n + 1 problem ( my ( $first, $second, $cycle_length ); my $max_cycle_length = 0; say "Input:"; chomp( my @pairs = <> ); say "Output:"; foreach my $pair (@pairs) { ( $first, $second ) = split( / /, $pair ); foreach my $num ( $first .. $second ) { $cycle_length = totalizer($num); if ( $cycle_length > $max_cycle_length ) { $max_cycle_length = $cycle_length; } } say "$first $second $max_cycle_length"; } sub totalizer { my $number = shift; $cycle_length = 0; { ++$cycle_length; last if ( $number == 1 ); if ( $number % 2 != 0 ) { $number = ( $number * 3 + 1 ); } else { $number = $number / 2; } redo; } return $cycle_length; }

The Wisdom of the Monks will be most welcome.

Replies are listed 'Best First'.
Re: General style advice requested
by toolic (Bishop) on Oct 22, 2010 at 02:32 UTC
    • It's a good practice to limit the scope of your variables. It looks like $first, $second and $cycle_length can be declared inside your 1st foreach loop. Similarly, $cycle_length can probably be declared in your sub.
    • $number = $number / 2; can be shortened to $number /= 2;. See Assignment Operators.
    • perlcritic is a tool which you can use to check for best practices. I ran it on your code, but nothing significant (in my opinion) was reported.
Re: General style advice requested
by JavaFan (Canon) on Oct 22, 2010 at 02:34 UTC
    Seems pretty ok to me. A few points:
    • I would not have made the program ask for input; instead I would read from STDIN. Neither would I print 'Output:' - just print the answer.
    • I would not use a bare block in totalizer. Instead I would write: while ($number > 1) {....}. I'd omit the != 0 as well.
    • You also may want to do some input validation. You're just assuming everyone will type in exactly two positive numbers; your program is bound to loop forever if someone offers '-1 -1' as input.
Re: General style advice requested
by ikegami (Pope) on Oct 22, 2010 at 02:45 UTC

    $cycle_length being declared globally is not acceptable. I would declare $first and $second in the loop, but they're acceptable where they are.

    The other issue it's very hard to spot the loop in your sub. May I recommend

    for (;;) { # I read this as "for ever" ... }

    or the more popular

    while (1) { ... }
Re: General style advice requested
by morgon (Curate) on Oct 22, 2010 at 03:14 UTC
    Your loop in the totalizer-sub is very unusual.

    I would write this sub like this:

    sub totalizer { my $number = shift; my $cycle_length = 1; while($number != 1) { $cycle_length++; $number = $number % 2 ? $number * 3 + 1 : $number / 2; } return $cycle_length; }
Re: General style advice requested
by snape (Pilgrim) on Oct 22, 2010 at 02:37 UTC

    Hi the program style looks good. If you are interested in writing your scripts using an editor then you may use Eclipse -- Classic version with Perl Interpreter. It is nice and really good for both error detection, debugging and formatting your code. You may have a look on the editor -- EPIC

Re: General style advice requested
by AnomalousMonk (Chancellor) on Oct 23, 2010 at 13:14 UTC

    This is a more direct link to the problem set on the streamtech site. The link given in the OP requires quite a bit of searching.

      I have updated the link in the original post.
Re: General style advice requested
by iangibson (Scribe) on Oct 23, 2010 at 17:03 UTC

    Thanks for your replies everyone. Here is what I now have:

    #!/usr/bin/perl use warnings; use strict; use 5.010; # The 3n + 1 problem ( my @pairs; say "Input:"; while (<STDIN>) { if (/^[1-9]+\d*\s[1-9]+\d*$/) { # check for and prevent illegal + entries chomp; push( @pairs, $_ ); } else { say "invalid input!" } } say "Output:"; foreach my $pair (@pairs) { my ( $first, $second ) = split( / /, $pair ); my $max_cycle_length = 0; if ( $first > $second ) { ( $first, $second ) = ( $second, $first ); } # doesn't matter whether the higher number is entered first o +r second foreach my $num ( $first .. $second ) { my $cycle_length_out = totalizer($num); if ( $cycle_length_out > $max_cycle_length ) { $max_cycle_length = $cycle_length_out; } } say "$first $second $max_cycle_length"; } sub totalizer { my $number = shift; my $current_cycle_length; for ( $current_cycle_length = 1 ; $number != 1 ; ++$current_cycle_ +length ) { if ( $number % 2 != 0 ) { $number = ( $number * 3 + 1 ); } else { $number /= 2; } } return $current_cycle_length; }

    I hope this is an improvement.

      Indeed an improvement. For contemplation here's something closer to what I would write:

      #!/usr/bin/perl use warnings; use strict; use 5.010; # The 3n + 1 problem ( my @pairs; say "Input:"; while (defined ($_ = <STDIN>) && !/^\.$/) { if (!/^(\d+)\s+(\d+)$/ && $1 > 0 && $2 > 0) { chomp; say "Invalid input >$_<."; say 'Two non-zero positive integers are expected. Input ignore +d'; next; } push @pairs, [$1, $2]; } say "Output:"; for my $pair (@pairs) { my $maxLength = calcCycleLen ($pair->[0]); my $length2 = calcCycleLen ($pair->[1]); $maxLength = $length2 if $maxLength = $length2; say "@$pair $maxLength"; } sub calcCycleLen { my ($number) = @_; my $length = 1; while ($number > 1) { $number = $number % 2 ? $number * 3 + 1 : $number / 2; ++$length; } return $length; }

      The first change is to add a stopping condition for command line input from STDIN. while (<$fooIn>) { is fine if $fooIn is connected to a file, but is less good for devices such as a keyboard where the end of file isn't clear.

      There are a few other changes of note in the while loop:

      1. the regular expression is used to capture the two numbers and the requirement for just one white space character is relaxed.
      2. A range test is performed on the numbers and provides an additional failure mode for the input
      3. The bad input is reported and the valid input criteria are specified
      4. There is no else part to the if statement. Instead the "early exit" technique is used. This very often reduces nested code and can make the main flow of the code much easier to see.
      5. The pairs are stored in their own two element array. @pairs thus contains arrays - it is an AoA (some people like to use lol instead).

      Moving on: as a personal preference I use for everywhere rather than a mixture of for and foreach - probably because I'm lazy.

      The @pairs for loop has changed a lot!

      1. $pair now is a reference to an array containing two numbers instead of a string.
      2. $maxLength is initialised to the first value that will be calculated. This trick is often used where a result is calculated by considering each element of a vector in turn. Initialise the result variable with the calculated value from the first element of the vector, then process the remaining elements in a loop.
      3. Calculate the second value. Note the syntax used to access the array elements from the $pair variable holding a reference to the array btw.
      4. Update $maxLength if it is smaller than the second length. Note the use of if as a statement modifier.

      Now we are in the home stretch.

      1. I use my ($number) = @_; because it scales better than a bunch of shifts. Not needed in this case, but it's what flows from my fingers.
      2. The C style for loop is almost unused in Perl. In this case a while loop better gives the flavour of what is going on anyway.
      3. The ternary operator is easy to abuse. In this case though it reduces some rather cluttered looking code to something that (in my view) is clear and concise.

      The final step is to pass Perl::Tidy over the code to clean up any wayward formatting, then enjoy.

      True laziness is hard work
      Thanks for the link :-) I also tried the first assignment yesterday, but with a small speed-up included. See for comparison:
      #!/usr/bin/perl use warnings; use strict; my %cache = (1 => 0); sub _three_n_plus_1 { no warnings qw/recursion/; my ($n,$count) = @_; $count ||= 1; return $count + $cache{$n} if exists $cache{$n}; if($n % 2){ $cache{$n} = _three_n_plus_1(3*$n+1, $count+1) - $count; return $cache{$n} + $count; }else{ $cache{$n} = _three_n_plus_1($n/2, $count+1) - $count; return $cache{$n} + $count; } } sub three_n_plus_1 { my ($i,$j) = @_; my $max = 0; for my $x ($i<$j ? $i..$j : $j..$i){ my $t = _three_n_plus_1($x); $max = $t if $t > $max; } print "$i $j $max\n"; }

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://866695]
Approved by BrowserUk
Front-paged by planetscape
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others pondering the Monastery: (1)
As of 2017-09-23 21:27 GMT
Find Nodes?
    Voting Booth?
    During the recent solar eclipse, I:

    Results (273 votes). Check out past polls.