Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

Primes. Again.

by Andrew_Levenson (Hermit)
on Apr 26, 2006 at 02:47 UTC ( [id://545670]=perlquestion: print w/replies, xml ) Need Help??

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

I stumbled across a new (to me) way of testing a number to see if it is prime, so I had to write a new program using it.

But it does not work. My computer is accusing me of dividing by zero. I'd yell at it, but it is undoubtedly my fault. I can't see why, though. Any clue as to why this does not work?
use strict; use warnings; my @prime; for my $i(2..100){ if($i==2){ push @prime, $i; } else{ push @prime, $i; for(@prime){ if(int($i/$_)==($i/$_) and $i!=$_){ for(@prime){ $_=~s/$i//; } } } } } for(@prime){ print "$_\n"; }

Thanks in advance.

Replies are listed 'Best First'.
Re: Primes. Again.
by ikegami (Patriarch) on Apr 26, 2006 at 03:12 UTC

    $_=~s/$i//; is converting some members of @prime into the zero-length string. The zero-length string gets interpreted as zero in a numerical context. It could be fixed by skipping blank lines as seen below.

    I took the liberty of making a few optimizations:

    • I simplified your if-prime expression.
    • I removed the non-special case of "2".
    • I simplified the loop and the regexp into a simple assignment.
    • I exit the loop early (via last) when appropriate.
    • I pluralized @prime to @primes.
    my @primes; for my $i (2..100) { $primes[$i] = $i; foreach (@primes) { if ($_ && $i % $_ == 0) { $primes[$i] = ''; last; } } } foreach (@primes) { next unless $_; print "$_\n"; }

    You could also avoid adding it in the first place, as shown in the following snippet.

    my @primes; NUMBER: for my $i (2..100) { foreach (@primes) { if ($i % $_ == 0) { # Divides evenly, so not prime. next $NUMBER; } } push @primes, $i; } foreach (@primes) { print "$_\n"; }

    The disadvantage of the latter is that you can't do if ($prime[$i]) to determine if $i is prime. The advantage is smaller memory usage and a (presumably) small increase in speed.

    Update: Cleaned up/Expanded the text.

    A reply falls below the community's threshold of quality. You may see it by logging in.
Re: Primes. Again.
by GrandFather (Saint) on Apr 26, 2006 at 03:14 UTC

    Your main issue was that you were blanking out non-primes, but then using them in calculations. The empty string part of the error message was a clue. The line number was another. You may like this cleaned up version of you code that fixes that problem, suppresses the blank lines that your original code would have produced, and reduces the nesting somewhat.

    use strict; use warnings; my @prime = (2); for my $i(3..100){ push @prime, $i; for(@prime){ next if ! length $_; next if int($i/$_)!=($i/$_) or $i==$_; $_=~s/$i// for @prime; } } print join "\n", grep {length $_} @prime;

    DWIM is Perl's answer to Gödel
      At the risk of being obnoxious, I must politely request:
      If you find the time, could you break down the following lines for me? They are too advanced for me to read.
      next if ! length $_;
      next if int($i/$_)!=($i/$_) or $i==$_; (I suppose I don't understand 'next')
      print join "\n", grep {length $_} @prime;

      Thanks.

        Not obnoxious at all! Healthy curiosity from a Perl newbie I'd have said.

        next is a statement used inside a loop to say "skip the rest of the loop code and start the next itteration".

        next if ! length $_;

        is a next statement that is modified by an if. It is only executed if the if condition is true.

        join concatenates the elements of a list together using the string supplied between each pair of elements.

        grep filters a list of elements and only returns the list of elements for which the condition (in {} brackets) evaluates true.

        Note that this is a very quick introduction to these concepts. I expect you will take a trawl through the tutorials section now looking for next, last, redo, join, grep, map and modifiers.

        You should look for those key words in The Camel too.


        DWIM is Perl's answer to Gödel
Re: Primes. Again.
by Tanktalus (Canon) on Apr 26, 2006 at 03:22 UTC

    Your method of deleting from your @prime list is a bit suspect. First, a minor cleanup. I'm removing the special case for "2" - partly because of premature optimisation, and partly because I hate special cases. And I'm introducing a debugging aid: a print statement to tell us where we are.

    use strict; use warnings; my @prime = 2; for my $i(3..100){ print "$i...\n"; push @prime, $i; for(@prime){ if(int($i/$_)==($i/$_) and $i!=$_){ for(@prime){ $_=~s/$i//; } } } } for(@prime){ print "$_\n"; }
    Running this tells me that the problem happens when $i is 5. That's because during the $i == 4 loop, you've gone and set $prime[3] from 4 to "" (removing the "4"). Instead, what you really want to do is not bother to push $i onto @prime unless you know it already to be prime:
    use strict; use warnings; my @prime = 2; for my $i(3..100){ print "$i...\n"; my $is_prime = 1; for(@prime){ if(int($i/$_)==($i/$_) and $i!=$_){ $is_prime = 0; last; } } push @prime, $i if $is_prime; } for(@prime){ print "$_\n"; }
    IMO, this is a perfect excuse to use List::MoreUtils' "any" function:
    use strict; use warnings; use List::MoreUtils qw/any/; my @prime = 2; for my $i(3..100){ push @prime, $i unless any { int($i/$_)==($i/$_) and $i!=$_ } @pri +me; } for(@prime){ print "$_\n"; }
    (Thanks again to borisz for pointing this out to me earlier.)

    I'm sure this can be optimised further (without drastic changes to your original algorithm), but I'll let you investigate this further.

Re: Primes. Again.
by Codon (Friar) on Apr 26, 2006 at 07:46 UTC
    For a really great Primes solution (short, elegant, fast), check out this article by merlyn. It starts with a very basic algorithm, then progresses on to a vector seive. At that point, it looses me in how it actually works, but that didn't stop me from borrowing it to make a program to color a recent Foxtrot comic strip. :-)

    Ivan Heffner
    Sr. Software Engineer, DAS Lead
    WhitePages.com, Inc.
Re: Primes. Again.
by TedPride (Priest) on Apr 26, 2006 at 06:00 UTC
    Here's a nice version for if you need to check a lot of large numbers to see if they're prime:
    use strict; use warnings; my ($c, $i) = 0; for ($i = 36544645654; $c < 100; $i++) { $c++, print "$i\n" if prime($i); } BEGIN { my @primes = (2); sub prime { my $n = $_[0]; my $p; my $rt = int sqrt $n; for $p (@primes) { return 0 if $n % $p == 0; return 1 if $p >= $rt; } for $p (($primes[-1]+1)..$rt) { if (prime($p)) { push @primes, $p; return 0 if $n % $p == 0; } } return 1; } }
    This version remembers the primes you've found so far, and only checks necessary numbers to see if they're prime. Yes, I know you could also start by knocking out all the /2, /3, /5, etc numbers in your range, but this doesn't improve speed very much, and adds complexity to your code.
Re: Primes. Again.
by johngg (Canon) on Apr 26, 2006 at 08:46 UTC
    Here is a script that implements the "Sieve of Eratosthenes" which is another way of finding primes. Eratosthenes was a Carthaginian mathematician living at around 250BC. I have used a vector but a list would work as well.

    use strict; use warnings; my $limit = shift or die "No limit supplied\n"; die "Limit is not integer\n" unless $limit =~ /^\d+$/; my $sqrtLimit = sqrt $limit; my $sieve = ""; vec($sieve, 0, 1) = 1; vec($sieve, 1, 1) = 1; vec($sieve, $limit, 1) = 0; my $reached = 1; while($reached < $sqrtLimit) { my $prime = $reached + 1; ++ $prime while vec($sieve, $prime, 1); print "$prime is a prime\n"; my $fill = 2 * $prime; while($fill <= $limit) { vec($sieve, $fill, 1) = 1; $fill += $prime; } $reached = $prime; } foreach my $value ($reached + 1 .. $limit) { print "$value is a prime\n" unless vec($sieve, $value, 1); }

    I hope this is of interest. It seems to run quite quickly.

    Cheers,

    JohnGG

    Update: I've just realised I had posted this script before in here.

Re: Primes. Again.
by thundergnat (Deacon) on Apr 26, 2006 at 15:34 UTC

    Another way of testing for primes is to use the prime regex. (For small numbers at least, it can exceed the recursion limit pretty quickly for larger numbers.)

    use strict; use warnings; sub prime{ (1 x shift) !~ /^1?$|^(11+?)\1+$/; } for (1..1000){ print "$_\n" if prime $_; }
Re: Primes. Again.
by TedPride (Priest) on Apr 26, 2006 at 17:44 UTC
    johngg: Your vector solution works, but it doesn't remember between function calls the primes it's found, and it must be doing something odd, since calling it for a large number like 234223345333 gives the message:
    # Assigning to negative offset in vec.
    Calling it for a somewhat smaller number like 234223345 just makes it run practically forever.

    EDIT: Never mind, I see the problem. You have to store a bit for each number that could potentially be a prime, so when the max number is extremely large, you apparently go out of bounds. Plus, hits on numbers that have already been punched out increase significantly after the first few primes, and I imagine it would be more efficient to pick some point after which to switch from skip counting to modulo testing.

      It was not really meant to be a heavy duty prime number finder but something compatible with my rather meagre mathematical skills. And after all, Eratosthenes never had to worry about going out of bounds in 250BC :-) On my ancient hardware I don't ask it to go beyond 10000000 as I don't have all day while it fills in all the multiples of 2 etc.

      FWIW, I am still trying to get my head around your method. I downloaded and ran it and was very impressed with it's speed on a 250MHz UltraSPARC, but I don't quite understand it yet. I'll get there eventually.

      Cheers,

      JohnGG

Re: Primes. Again.
by TedPride (Priest) on Apr 27, 2006 at 06:51 UTC
    You start by taking the int of the square root of the number you want to test for prime. This is the largest number that could potentially divide cleanly into your number without a smaller number also dividing cleanly. Then you check all the primes currently stored, up to that max, to see if they divide into your number cleanly. If they do, you return 0 - no further processing is necessary. If you reach a prime larger than max, you return 1 - the number is a prime.

    But what if there aren't enough primes in your array to check all the way up to max? You need to calculate those primes so you can check them. For all numbers from the current max stored prime + 1 to the square root of the potential prime, you check each number to see if it's a prime - using the same function! You add these primes to the stored primes array, and also check each one to see if it divides cleanly into your number. If it does divide cleanly, no further calculations are necessary, and the function returns 0.

    Bottom line, only necessary calculations are made (as far as I can tell), minus a few minor tweaks such as skipping even numbers.

      Sorry for the slow reply, today was full of meetings.

      I already had the first paragraph of your explanation as it is the same in essence as my solution, except that mine spends a lot of time filling out the sieve for each prime it finds. What I couldn't see to start with was why your solution was so quick. I was thinking "how can it fill in all the primes up to 36544645654 in that time?" However, as you point out, and I had managed to work out, you are only filling in the primes up to the square root of 36544645654 (191000 or so).

      No wonder it is so much faster. Very nice.

      Cheers,

      JohnGG

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://545670]
Approved by GrandFather
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (4)
As of 2024-04-20 14:28 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found