My flight out of Baltimore was delayed with mechanical trouble, so I set to work on a one liner to generate prime numbers, in the spirit of One line Fibonacci generator. Unfortunately, I was unable to get it down to a one liner, but it's still pretty damn compact and I am rather proud of it even if it didn't meet my goal of the single statement. 95 characters isn't that bad. And it's pretty fast, too.

++$";while($\=$/and$"++and$;=$"){ do{$;&=$|and last if!($"%$_)}for@_; push@_,$"and print$"if$;}

Okay, this one I'll explain for those that are curious.

There are two normal ways of calculating prime numbers - the sieve of eratosthenes and by checking all divisors. The sieve works by taking a list of numbers (say 1-500) and marking off all multiples. So you start with "2", and mark off 4, 6, 8, 10, 12, 14 and so on. Then you go to 3 and mark off 9 (6 was already marked off), 15, and so on. It's very very fast.

The other normal way is to check all possible divisors. So if you want to see if 14 is prime, you divide it by 2,3,4,5,6,7,8,9,10,11,12,13. If any of those go into it evenly, it's not prime and you skip it. if none of them do, it's prime and you're done.

The first optimization that most CS 102 students add in for this problem (it's a standard intro to CS task) is to cleverly check only up to NUMBER / 2. Because your lowest multiplier is always 2, if the multiplicand is greater than half of your number, then the product will always be higher. Now we're only checking to see if 14 is divisible by 2,3,4,5,6,7. Cut our work in half.

The next step to optimize the process is to realize that the multiplier can never be greater than the square root of the number. The square root times any number smaller than it will be smaller than the number you're checking, and the square root times any number greater it will be greater than the number you're checking. The square root of 14 is 3.74165, so we can get away with only checking 2 and 3. Much less work.

Most people stop here, but there's another step you can take. You actually only need to check all of the prime numbers that are less than the square root. For example, if the number is 64, you don't need to check and see if 64 / 8 (which it is) because you know that 8 is divisible by 2. So if 8 divides into 64, you know for a fact that 2 divides it as well. So only checking the primes radically decreases your count. In the example of 14, it's still only 2, and 3. For 64, instead of checking 2,3,4,5,6,7,8 you would now only check 2,3,5,7. (Note that it bombs out at 2 anyway).

There's a additional trick I know that I rarely use. You can take the number mod 6. Prime numbers mod 6 are only either 1 or 5, never the others. Breaking it down, if X % 6 is...

remainderX is divisible by
06
1(unknown)
22
33
42
5(unknown)

There may be other modulus tricks along these lines.

Now for how this script operates.

First, we'll re-format it into something easier on the eyes.

++$"; while($\= $/ and $"++ and $;=$" ) { do{ $; &= $| and last if ! ($" % $_) } for @_; push @_, $" and print $" if $; }

We arbitrarily choose to use some built in "noisy" variables to make it more obfuscated. We start off by incrementing $". That's normally empty, so that sets it to 1. $" will be our number that we're checking for primeness

Entering our while loop, we have:

while($\= $/ and $"++ and $;=$" ) {

The first step is just there to confuse you - it sets the output record separator to the input record separator ("\n" on unix systems) to allow the output to have linebreaks. This could easily be before the loop. Next, we increment $". Since it's already 1 (not prime), we start checking at 2. Finally, we use $; as our "break" operator and arbitrarily set it to $", which is a known true value.

do{ $; &= $| and last if ! ($" % $_) } for @_;

Here's where the work is done. @_ is going to contain the list of all prime numbers we've found so far. This is that final optimization listed up above - we're only going to check the primes less than our current number. We discard the optimization of looking up through to the square root for compactness purposes.

And it's easy. Check every prime. If the number we're checking ($") modulus the prime we're looking at ($_) is 0, then the number is not prime. In that case, set our arbitrary true value ($;) to something false. We do this by $; &= $|, since $| is normally set to false. We then escape from the loop. The if ! is more clearly written as an unless.

push @_, $" and print $" if $;

Finally, if our arbitrary true value ($;) is still true (meaning, we found no successful divisors), then it's prime. So we add it to the stack of primes (@_) and print it out. Wash, rinse, repeat.

Replies are listed 'Best First'.
Re: Prime number generator
by liverpole (Monsignor) on Jul 17, 2006 at 19:06 UTC
    Hi jimt,

    Here's a 23-character script that, for any integer input, will output a prime number:

    $_=$=-(shift)**$[;print

    Okay, maybe that's cheating ... :-)

    Here's a prime-number generator in 65 characters.  I wouldn't be surprised if it can be golfed further...

    $o=1;{++$o&&grep{$o/$_==int$o/$_}keys%_ or$_{$o}=print$o.$/;redo}

    Update:  Make that 64 characters...

    $o=1;{++$o&&grep$o/$_==int$o/$_,keys%_ or$_{$o}=print$o.$/;redo}

    s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/
      ..and even 57:
      $o=1;{++$o&&grep!($o%$_),keys%_ or$_{$o}=print$o.$/;redo}

           s;;Just-me-not-h-Ni-m-P-Ni-lm-I-ar-O-Ni;;tr?IerONim-?HAcker ?d;print
        Very slick! ++

        It looks even nicer, I think, without whitespace, though I can't find a way to improve on your 57 ... yet! ...

        $o=1;{$_{$o}=print$o.$/if++$o&&!grep!($o%$_),keys%_;redo}

        Update:  Aha!  It took me some thinking, but here's 56 ...

        $o=1;{$_{$o}=print$o.$/if++$o,!grep!($o%$_),keys%_;redo}

        s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/
Re: Prime number generator
by rhesa (Vicar) on Jul 17, 2006 at 20:33 UTC
    $o=1;{(grep!($o%$_),2..++$o/2)||print$o.$/;redo}
    That's 48.

    if we allow a shebang line:

    #!/usr/bin/perl -l $o=1;{(grep!($o%$_),2..++$o/2)||print$o;redo}
    45 :)
      Stunning! ++

      But you realize, of course ...

      #!/usr/bin/perl -l $o=1;{(grep!($o%$_),2..$o++)||print$o;redo}

      That's 43.

      It's a little slower (but then we sacrificed speed a while back ;-))

      Update:  Make that 42 ...

      #!/usr/bin/perl -l $o=1;{grep!($o%$_),2..$o++or print$o;redo}

      s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/
        Hey, making it post-increment is clever! Well spotted :)

        I had a hard time getting those stupid brackets out of there. At some point, I realised that I was being bitten by buffered output, and didn't think about them anymore.

        I think 42 is perfect!

        Cool :) ++

             s;;Just-me-not-h-Ni-m-P-Ni-lm-I-ar-O-Ni;;tr?IerONim-?HAcker ?d;print
Re: Prime number generator
by Ieronim (Friar) on Jul 17, 2006 at 21:47 UTC
    Just for the sake of completeness - my 58-char sieve of Eratosthenes. The one-liner prints all integers less than given argument.
    @_=(2..shift);while(@_){print$_[0].$/;@_=grep{$_%$_[0]}@_}

         s;;Just-me-not-h-Ni-m-P-Ni-lm-I-ar-O-Ni;;tr?IerONim-?HAcker ?d;print
Re: Prime number generator
by truedfx (Monk) on Jul 22, 2006 at 11:42 UTC

    I have no idea who it was that originally came up with the idea of regexes, but I liked it and it seems appropriate here, so:

    #!perl -p $_ x=(1x$_)!~/^(|.|(..+)\2+)$/
    $ seq -50 50 | perl prime.pl 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47
Re: Prime number generator
by mr_mischief (Monsignor) on Jul 22, 2006 at 00:55 UTC
    I'm getting behavior our of perl I can't explain. Is this acting strangely for anyone else? The following one doesn't print a thing:

    {for(2..$p){$i%$_ or$p=0}print$i if$p;$p=$i++,redo}
    and neither does:
    {for(2..$p){$i%$_ or$p=0}print"$i" if$p;$p=$i++,redo}
    but when I try this:
    {for(2..$p){$i%$_ or$p=0}print"$i$/" if$p;$p=$i++,redo}
    it works just fine. I'm on perl 5.8.7 on both Mandriva Linux and ActiveState.


    Christopher E. Stith
      Are you using the -l command line switch? Are you patient enough? Buffering might prevent the output from appearing straight-ahead. Try adding a $|++; to the start of first two alternatives to make it autoflush.
        It was the buffering. Autoflush made it work like a champ. I simply hadn't considered that. Thanks.


        Christopher E. Stith