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

I was struggling to understand the "Sieve of Eratosthenes", as I'm really very bad at math. Here is the recipe I followed to create the following script:
#!/usr/bin/perl use strict; use warnings; $\ = $/; my %not_prime = (); my $limit = 100; # change this to $ARGV[0] if required foreach my $x ( 2 .. $limit ) { # for all numbers 2 to the maximum required next if $not_prime{$x}; # skip numbers already known not prime print "current prime is $x"; my $y = $x; while ( ($x + $y) <= $limit ) { print " * crossing off " . ($x + $y) . ", a multiple of $x"; $not_prime{ $x + $y } = 1; $y += $x; } } print "Primes between 2 and $limit:"; for ( 2 .. $limit ) { unless ( $not_prime{$_} ) { print "$_"; } }
I thought Monks might like to come up with a shorter version?


($_='kkvvttuubbooppuuiiffssqqffssmmiibbddllffss')
=~y~b-v~a-z~s; print

Replies are listed 'Best First'.
Re: Sieve of Eratosthenes Golf?
by blokhead (Monsignor) on Apr 07, 2005 at 04:04 UTC
    Here's 58 57 characters that even does all the printing, though it needs to be run with perl -l:

    sub f{@_?($_[0],f(grep$_%$_[0],@_)):()}print for f 2..pop

    I used a recursive filtering approach to make it much more concise. In fact, the sub that does the work is only 39 characters.

    Update: I noticed an easy way to shave a stroke (grep EXPR vs grep BLOCK), so now it's at 57. I also added code tags which I stupidly omitted.

    blokhead

      The guys above you seem to have proceeded on the assumption that they should write (the contents of) a sub which returned the correct numbers, whereas yours prints them and you've included the sub block too. Not sure what the rules of "golf" are at this point but you're definitely ahead!


      ($_='kkvvttuubbooppuuiiffssqqffssmmiibbddllffss')
      =~y~b-v~a-z~s; print
      That's a very interesting take on the Sieve. Rewritten to be golfed, it's 33 characters.
      @_?($_[0],f(grep$_%$_[0],@_)):()

      It needs to be called as f(2..$limit) vs. f($limit).

      If the requirement is that the signature is f($limit), then it goes to 47 characters
      sub x{@_?($_[0],x(grep$_%$_[0],@_)):()}x 2..pop

Re: Sieve of Eratosthenes Golf?
by dragonchild (Archbishop) on Apr 07, 2005 at 03:05 UTC
    I'll start the bidding at 99 characters.
    my%x;my$l=pop;for(2..$l){$x{$_}||=0||do{my$m=2*$_;{$x{$m}=1;($m+=$_)<= +$l&&redo}}}grep!$x{$_},keys%x
      Unneeded variable declaration, 9 stroke penalty!
      $x=pop;for(2..$x){$x{$_}||=0||do{$m=2*$_;{$x{$m}=1;($m+=$_)<=$x&&redo}}}grep!$x{$_},keys%x
Re: Sieve of Eratosthenes Golf?
by BrowserUk (Patriarch) on Apr 07, 2005 at 06:28 UTC

    It probably won't win the golf, but it will go a lot higher than most. It'll handle upto 1,000,000,000 if you have 1 GB of ram.
    sub f{ #23456789 123456789 123456789 123456789 123456789 123456789 $_=1x$_[0];for$n(1..sqrt$_[0]){m[^.{$n}.]g;s[\G(.{$n}).][${1}0]g;} } f(pop); ++$i and $1 and print $i while m[(.)]g;

    And this one will go eight times higher in the same space, and should be relatively quick.
    sub f{ $x="\xff"x$_[0];for(2..$_[0]){$n=$_;vec($x,$n,1)=0 while($n+=$_)<$_[0] +;} } f( $ARGV[0]); vec$x,$_,1 and print $_ for 1..$ARGV[0];

    You could double that by not storing the evens,


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco.
    Rule 1 has a caveat! -- Who broke the cabal?
      I don't understand those at all really, but the second one did a million in about a minute, so not bad at all. It did tell me that a million was a prime number though...


      ($_='kkvvttuubbooppuuiiffssqqffssmmiibbddllffss')
      =~y~b-v~a-z~s; print
        It did tell me that a million was a prime number though...

        Due to golfing it, it doesn't handle the very last number correctly.

        I don't understand those at all really,

        They both work in essentially the same way. The first one creates a string of $limit '1's. It then uses a regex to scan the string substituting '0's for '1's, for all the multiples. It then scans the string, incrementing a counter, looking for the remaining '1's and prints out the counter when it finds a '1'. It just uses the big string as a tally stick and 'crosses off' all the multiples.

        The second one does the same thing, but uses vec, and bits instead of bytes, to represent the tally stick.

        By avoiding building big lists/arrays, it allows you to go 30/250 or so times higher.

        If you're not golfing, it is easy to omit the even bytes/bits and therebye double the range again.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        Lingua non convalesco, consenesco et abolesco.
        Rule 1 has a caveat! -- Who broke the cabal?
Re: Sieve of Eratosthenes Golf?
by cog (Parson) on Apr 07, 2005 at 09:50 UTC
    My first solution, in which I explicitly include the number 100, is at 85 characters, to be run with perl -l.

    for(2..100){$n{$y=$_}&&next;$n{$y+=$_}=1 while($_+$y)<=100}for(2..100){$n{$_}||print}

    More^WLess to come, I hope.

      OK, I know I'm twisting things a little, but I don't consider the problem to be well formed anyway (what is the objective? to print all the prime numbers between 2 and 100? to implement the sieve of Erathosthenes? in a sub? with which output?)

      Anyway, here's a basic enhancement, and I also trimmed two characters with s/100/99/ (hey, we all know 100 is not prime, right? O:-) )

      for(2..99){$n{$y=$_}&&next;$n{$y+=$_}=1 while($_+$y)<=99;$n{$_}||print

      I'm now at 71 chars.

        strict and warnings compliant at 77 characters ...
        $_{$.=$_}||do{{($_{$.+=$_}=$.+$_<=$,)&&redo}}for@.=2..($,=pop);grep!$_ +{$_},@.
Re: Sieve of Eratosthenes Golf?
by tilly (Archbishop) on Apr 07, 2005 at 13:52 UTC