I've recently started doing the
Perl Weekly Challenges. This one
was to hunt primes and do "Vigenère" enciphering.
The first part asked for the first ten strong and weak prime
numbers; a strong prime is one that's greater than the mean of its
neighbouring primes.
So the first step was to generate a bunch of prime numbers, for which
I used the Sieve of Eratosthenes, storing candidates in a hash. (It's
inefficient, but at this size that doesn't really matter.)
my $cap=110;
my $limit=10;
my %n=map {$_ => 1} (2..$cap);
foreach my $f (2..int(sqrt($cap))) {
map {($f<$_ && $_%$f == 0)?delete $n{$_}:0} keys %n;
}
my @p=sort {$a <=> $b} (keys %n);
Obviously I could have calculated all the means, but that would have
been boring, not to mention needing floating-point arithmetic. So
instead I did a double difference on my list. The first difference
gives me a list of the differences between primes (i.e. 2, 3, 5, 7,
11, 13 gives 1, 2, 2, 4, 2); the second difference gives me a list of
the differences between those (1, 0, 2, -2). If that second
difference is less than zero, the difference from the prime below to
this prime is more than the difference from this prime to the prime
above… i.e. it's a weak prime.
my @d1=map {$p[$_+1]-$p[$_]} (0..$#p-1);
my @d2=map {$d1[$_+1]-$d1[$_]} (0..$#d1-1);
my @res;
foreach my $k (0..$#d2) {
my $i=1;
if ($d2[$k]<0) {
$i=0;
}
push @{$res[$i]},$p[$k+1];
}
my @l=qw(Strong Weak);
if (scalar @{$res[0]} >= $limit && scalar @{$res[1]} >= $limit) {
foreach my $m (0,1) {
splice @{$res[$m]},$limit;
print $l[$m],': ',join(', ',@{$res[$m]}),"\n";
}
} else {
warn "Re-run with a higher cap value\n";
}
And that's all there is to it.
The other challenge was to implement a
Vigenère cipher,
really a Bellaso, i.e. a keyed polyalphabetic Caesar cipher. We want a
key and a mode switch (encrypt/decrypt, since it's not symmetric).
use Getopt::Std;
my %o;
getopts('dk:h',\%o);
# Ensure we have a key
unless ($o{k}) {
$o{h}=1;
}
if ($o{h}) {
print STDERR <<EOF;
Usage: $0 -k KEY (-d)
Use -d to decrypt.
Encryption/decryption is from stdin to stdout.
EOF
exit 0;
}
Build a table of the valid alphabet, and letter-to-number mappings
since we'll be using them.
my @alphabet=('A'..'Z');
my %tonumber=map {$alphabet[$_] => $_} (0..$#alphabet);
I'll bring in the strip function now, since it gets used later. It
just reduces its input to upper-case letters.
sub strip {
my $r=shift @_;
$r=uc($r);
$r =~ s/[^A-Z]//g;
return $r;
}
Strip the key, and further strip out any repeated letters (a key of
"BASTARD" is functionally identical to a key "BASTRD").
$o{k}=strip($o{k});
my %ak=map {$_=>1} @alphabet;
my @k;
foreach my $kl (split '',$o{k}) {
if (exists $ak{$kl}) {
push @k,($o{d}?-1:1)*$tonumber{$kl};
delete $ak{$kl};
}
}
my $keylen=scalar @k;
Note the trick in there - that's the only place where the code path
varies for decryption vs encryption mode. For encrypting with BASTRD
I'd store (1, 0, 18, 19, 17, 3), position in the alphabet starting at
zero; for decrypting, (-1, 0, -18, -19, -17, -3).
Now we pull in the input, strip it down, and map the letter sequence
to numbers; then add the offset from the appropriate point in the key
(modulo 26, which does the whole job of the tabula recta), and
convert it back to a letter.
while (<>) {
chomp;
my @pt=map {$tonumber{$_}} split '',strip($_);
my @ct;
foreach my $n (0..$#pt) {
push @ct,$alphabet[($pt[$n]+$k[$n%$keylen])%26];
}
print join('',@ct),"\n";
}
Spot the bug! The key position resets at the end of each line. Which
is entirely adequate for toy encryption, and Vigenère isn't
now appropriate for anything else, so I won't bother to fix it.
An implementation of
Playfair would be
more of a challenge to write, because of all its special-case rules
about what to do when the encoding square becomes degenerate. (Noting
that there's a good cryptanalysis of this in Sayers' Have His
Carcase from 1932, so it's similarly inappropriate for anything
except a toy.)