Michael Mol (mmol_6453) wrote,
Michael Mol
mmol_6453

Primes, sums and my first experiment with Perl 6.

Instead of counting sheep while I try to fall asleep, I've taken to counting sums of primes.

The first ten primes are: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29.
The first ten sums, then, are: 2, 5, 10, 17, 28, 41, 58, 77, 100, 129.

In my falling-asleep state, I made a number of math errors in my head, and thought I'd noticed a recurring pattern or two. I wrote a script to generate primes and the sum sequence. I used the opportunity to learn a bit about Perl 6. Particularly, intentionally exercising Perl 6 features I didn't necessarily need to exercise:

multi prime (Int $n --> Bool) {
$n %% none 2, 3, *+2 ...^ * > sqrt $n;
}
 
multi prime (Int $n where ( $n <= 1)) {
False;
}
 
sub next_prime(Int $start --> Int) {
my $check = $start + 1;
if ( $check %% 2 and $check > 2 ) { ++$check }
 
until(prime($check)) { $check += 2 }
 
return $check;
}
 
my @primes := 2, -> $a { next_prime($a) } ... *;
my $prime_idx = 1;
my @sums := 2, -> $a { $a + @primes[$prime_idx++] } ... *;
 
for @primes Z @sums -> $prime, $sum { say "$prime -> $sum" }

So, here are my thoughts and observations. First, the two lines that begin with "multi prime" started off as a primality test function from Perl 6's entry on Rosetta Code for Primality by Trial Division.

I don't remember where I saw it, but the multi keyword's approach for overloading methods based on preconditions seemed like a useful alternative to checking for the value of $n within prime(). I like the idea of being able to guarantee function inputs' validity before the function body gets called; that let's me keep my code cleaner. Also, I like that the prime() instance that handles the $n <= 1 case has a very short body; hopefully, any optimizer that ever sees a function body like that might be able to take advantage of its simplicity. Here, the program uses multi to identify special case arguments and exit early.

I can also see using the multi approach to catch special case argument data, sanitize it to a general case value, and then call itself (well, the function by its name) again, returning the value of the recursive call. I tried playing around with this for next_prime, and while it allowed the real guts of the function (the until loop) to operate without in-code-block input validity-check code (and looked somewhat cleaner as a result), I found the alternative next_prime instances I'd written to be potentially difficult to understand if one was trying to figure out code flow in advance.

The next time I try using multi for advance sanitization of inputs, I'll be sure to organize the function bodies in "santization/special-case" and "execution" sections in my file, so it's obvious what's going on.

The general-case prime() instance itself is interesting, too. "%%" acts like a Boolean modulo that returns True if there is no remainder, and False if there is a remainder. Think of it like "(0 == (x modulo y))", or "(0 == (x / y) - (floor(x / y)))" if you like.

The keyword none on the right of %%, I believe, requires that %% be run against each of its list arguments. I can read from it that its list arguments are 2, 3, ... and something that equates to a sequence of a set of numbers.

About that sequence...I assume that it's supposed to only check against odd numbers. The *+2 suggests that to me, and it makes sense; no prime number will be even, and any division of a number by a number known to not be prime is a wasted calculation. One thing about prime numbers...calculating them tends to be a mathematically slow process.

The ... indicates that we're talking about a sequence. I don't know exactly how *+2 ...^ * > sqrt $n parses, except that (unless there's a bug in it) I expect it contextually means "all odd numbers up to and including the square root of $n." (That's the other thing about division tests; the biggest real integer number that can possibly be a factor of another real integer number is the latter's square root, so checks with larger numbers are again waste.) The bit about them only being odd numbers may stem from prime() already having rejected any even ones. Apart from that, I don't understand it well enough to have written it.

On to the next_prime subroutine. This one is simple enough; I wrote it to return the first prime number greater than the number given as an argument.

The first thing it does is make a local copy of its argument, plus one. I found that next_prime attempted to modify its argument, and would fail if I called it with a numeric constant, as in next_prime(1). It then increments the local copy  If you're a Perl programmer, you might notice that I used my. In Perl 6, that's a lexically-scoped variable, not a package variable. I understand it as being block-scoped, but that's probably my rudimentary understanding coming from a primarily-C/C++ background.

If, after having already incremented the variable once, the variable happens to be even (and not 2), I increment it again--there are no even prime numbers apart from 2, and this saves us calling prime() on a number known not to be prime.

Now we get to our until loop. until(condition) is like while(!condition), though I did discover I had to say until(condition) {code block}, instead of {code block} until(condition), or the code in the block wouldn't change the value of the $check variable.

In my until loop, I increment by 2 each time we find that the number we're testing isn't prime; by this point, we're only going to see odd numbers, we know that any even number (apart from 2, which we won't see there) is not prime, and adding 2 to an odd number will get us to the next odd number--this way, we don't call prime() on any more even numbers.

Now to get the primes itself. Perl 6 allows one to define a sequence as a block of code, and with that sequence's symbol having all the properties of an array. @primes is defined as a sequence that uses next_prime to get each value.

I wanted to define @sums using the same functionality, but the only way I figured out how to do it (use another variable to keep track of the index to the value of @primes we're referencing) feels inelegant somehow. Still, it works.

Finally, we use the Z (well, technically Z,) metaoperator, which returns a nested array whose elements are pairings of the arrays to either side of the Z. (I'm being imprecise; you should read this, instead.) This lets us easily build a 'foreach' loop for running through our primes and sums simultaneously.

So here's the beginning of the output of my script:
2 -> 2
3 -> 5
5 -> 10
7 -> 17
11 -> 28
13 -> 41
17 -> 58
19 -> 77
23 -> 100
29 -> 129
31 -> 160
37 -> 197
41 -> 238
43 -> 281
47 -> 328
53 -> 381
59 -> 440
61 -> 501
67 -> 568
71 -> 639
73 -> 712
All told, I let this run through the first hundred primes or so. I didn't see anything I was critically convinced was special. If you're particularly interested in sums of prime numbers, check this out. (Thanks to opticron from #rosettacode for finding that for me.)

Subscribe

  • OK, WebGL sucks.

    At least, for now. I just watched something I honestly hadn't seen since 1997, when I was running Windows 95 on a machine that didn't even have 2D…

  • Managing open-source projects

    My brother wanted to know how to manage a large open-source project when, in his words, you have programmers who can come and go with little warning.…

  • Gentoo, as a VM host and VM guest

    So I've got kvm working fine under Gentoo.[1] Right away, I know that I want to start moving system services off of wash (my Debian router which is…

  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 1 comment