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

Hey monks. Ive been getting back on the perl bandwagon after about 2 years. My last (very small) attempts were based solely on editing and modifying someone elses code during uni but this time Ive been working through the Llama book and doing it properly. I want to spend more time doing and less time reading so Ive been getting my family to set me problems to fix as I go along (chapter 4 now!).

Seen as there are no right answers if you guys and gals see any errors in style or superfluous-ness (its a word!) I'd appreciate any feedback.

This was to work out all the factors of a number:
#!/usr/bin/perl use warnings; print "Type number you want factorised: \n"; chomp($no = <STDIN>); @int = (1..($no/2)); print "The factors of $no are: \n"; foreach $int (@int) { $div = $no/$int; $remainder = $no % $int; if ($remainder == 0){ print "$div\n"; } }
Thanks for looking M

Replies are listed 'Best First'.
Re: First code - Factors
by GrandFather (Saint) on Sep 22, 2010 at 11:02 UTC

    Using warnings is a good start, but the rest of the strictures mantra is to use strict. Personally I like a little more white space around the place and have tuned Perl::Tidy to do it the way I like it.

    You should avoid overloading variable names. You use both $int and @int. In some cases that can lead to very subtle and hard to find bugs. In fact for this code you don't need the array at all, just plonk the range straight into the for loop list.

    Making those few adjustments gives:

    #!/usr/bin/perl use strict; use warnings; print "Type number you want factorised: \n"; chomp (my $no = <STDIN>); print "The factors of $no are: \n"; foreach my $int (1 .. $no / 2) { my $div = $no / $int; my $remainder = $no % $int; if ($remainder == 0) { print "$div\n"; } }
    True laziness is hard work
Re: First code - Factors
by Khen1950fx (Canon) on Sep 22, 2010 at 11:58 UTC
    Good! And here's another way to do it.
    #!/usr/bin/perl use strict; use warnings; use Data::Dumper::Concise; use Math::Factor::XS qw(factors); my $number = shift @ARGV; print Dumper(my @factors = factors($number));
Re: First code - Factors
by roboticus (Chancellor) on Sep 22, 2010 at 13:59 UTC

    maebuf:

    You've already received some good comments. I'll just make a couple here:

    • In your loop, you're doing a divide and a modulus every iteration. You could do the divide only when the modulus leaves no remainder.
    • You don't really need to store the result of the modulus in a variable since you're using it only in your if statement. So you could just move the calculation to your if statement.
    • Similarly, you're creating an array from a sequence, and then using the sequence only in one place. You could put the sequence in the for loop where you use it.

    Putting those together would give you:

    foreach $int (1 .. ($no/2)) { if ($no%int == 0) { $div = $no / $int; print "$div\n"; } }

    Algorithmically, you're doing quite a few operations that will *never* give you a factor, because your sequence includes even numbers other than two. You could generate a list that includes 2 and all the odd numbers up through half of the input number like this:

    foreach $int (2, map {$_*2+1} (1 .. ($no/4)) { ....

    I hope you find it helpful.

    ...roboticus

    Update: If you want to crunch the code down, you could even combine most of it to a single statement:

    print "$_\n" for grep { 0==$no%$_ } (2, map { 2*$_+1 } 1 .. $no/2);

    Note: Another thing to watch for is that you're not restricting yourself to prime factors. I don't know if it matters to you, though.

Re: First code - Factors
by Anonymous Monk on Sep 22, 2010 at 10:58 UTC
    You could collapse most of that into print "$_\n" for grep { 0 == $no % $_ } @int;, if you wanted to. But I have my doubts whether this is reliable for large numbers, because you'll be comparing zero to a tiny floating-point number, which is usually a bad idea.

    Whether or not this is a toy program, you should also look at Math::Big::Factors: eg, factors_wheel($no, 1). Here's a hint in using the output of that function: increment hash values for each repeated factor returned, and you'll get a complete list of factors and their multiplicities.

      You could collapse most of that into print "$_\n" for grep { 0 == $no % $_ } @int;, if you wanted to. But I have my doubts whether this is reliable for large numbers, because you'll be comparing zero to a tiny floating-point number, which is usually a bad idea.
      What tiny floating-point numbers? '%' will always yield an integer, and since @int doesn't contain numbers exceeding $no, even $no/$_ for @int does not involve floats smaller than 1.
        Oh, you're right. I was thinking of the technique of naively dividing a number by a factor, without my paying attention to the code. Thanks!
Re: First code - Factors
by TomDLux (Vicar) on Sep 22, 2010 at 14:45 UTC
    I think you're objective is to write your first coed, rather than to compress it to maximal density, so I'll limit my suggestions to:
    • use strict
    • declare variables using 'my'.
    • use 3 or 4 spaces per indent level, they accumulate fast once you get into longer programs.
    • At about this level, you might start breaking your program into routines ...
    #!/usr/bin/perl use warnings; my ( $number ) = read_user_number(); my ( $factors ) = factor( $number ); output_number( $factors );

    Yes, I've added a complication ... though we generate an array of factors, I return and pass a reference to the array, rather than the list of values.

    As Occam said: Entia non sunt multiplicanda praeter necessitatem.

Re: First code - Factors
by mabeuf (Novice) on Sep 23, 2010 at 10:38 UTC

    Cheers for all the pointers guys, really helpful! Since posting I've been carrying on with the book and learned about strict and my and after you all pointing them out they'll be in common use now! Plus, I can see how I've repeated alot of the calculations when they're not needed. Haven't got as far as grep yet but I can see the gist from this, and modules are pretty far in my future I think.

    To summarise, B- for first code, A+ for personal development!

    Thanks, M