I’ve been doing the Perl Weekly
Challenges. The
latest
involved sorting email addresses and solving a variant N-queens problem.
Write a script that takes a list of email addresses (one per line)
and sorts them first by the domain part of the email address, and
then by the part to the left of the @ (known as the mailbox).
Note that the domain is case-insensitive, while the mailbox part is
case sensitive. (Some email providers choose to ignore case, but
that’s another matter entirely.)
If your script is invoked with arguments, it should treat them as
file names and read them in order, otherwise your script should read
email addresses from standard input.
Bonus
Add a -u option which only includes unique email addresses in the
output, just like sort -u.
The trick here is to construct an easily-sortable representation. This
code will fail on some of the more exotic constructs possible in a
user-part.
my %o;
getopts('u',\%o);
my %l;
while (<>) {
chomp;
my @e=split /@/,$_;
my $k=lc($e[1]).'@'.$e[0];
That's the sortable representation. billG@microSOFT.com becomes
microsoft.com@billG – which I can feed to a standard sort to get the
desired results.
If we're being unique, delete any previous addresses with this key.
if ($o{u}) {
delete $l{$k};
}
Under this key, store a list of email addresses that match it.
push @{$l{$k}},$_;
}
Then for output just look at each key and print the addresses stored
under it.
foreach my $k (sort keys %l) {
print map {"$_\n"} @{$l{$k}};
}
In Raku it was more work to sort out the option parsing than to
rewrite the code. The built-in libraries will let me have command-line
options, certainly – but not, at least not trivially, combined with
raw filenames. So in this first part I iterate through the
command-line arguments, putting any that are the names of files into
the files list, and parsing "-u" separately.
(If there exists a file called "-u"… frankly, you deserve all you get.)
my $u=0;
my @fn;
for @*ARGS -> $p {
if ($p.IO.e) {
push @fn,$p;
} elsif ($p eq '-u') {
$u=1;
}
}
unless (@fn) {
push @fn,'-';
}
The rest is basically the same algorithm, with syntactic tweaks for Raku.
my %l;
for @fn -> $fn {
my $fh=open :r,$fn;
for $fh.lines {
.chomp;
my @e=comb(/<-[@]>+/,$_);
my $k=lc(@e[1]) ~ '@' ~ @e[0];
if ($u) {
%l{$k}:delete;
}
push %l{$k},$_;
}
close $fh;
}
for (sort keys %l) -> $k {
my @q=%l{$k}.flat;
for @q -> $e {
say $e;
}
}
It is possible to place 8 queens on to a single chessboard such that
none of the queens can attack each other (i.e., their shaded squares
would not overlap). In fact, there are multiple ways to do so, and
this is a favourite undergraduate assignment in computer science.
But here at PWC, we’re going to take it into the next dimension!
Your job is to write a script to work with a three dimensional chess
cube, M×M×M in size, and find a solution that maximizes the number
of queens that can be placed in that cube without attacking each
other. Output one possible solution.
I took a two-stage approach to this: first, solve the N rooks problem,
then check the results for diagonality.
my $n=$ARGV[0] || 4;
my @a;
my $o;
my $m=0;
my @ap=([1,2],[0,2],[0,1]);
do {
my $r=[];
my @u;
my $cm=-1;
if (@a) {
$r=shift @a;
foreach my $c (@{$r}) {
foreach my $api (0..2) {
$u[$api]{$c->[$ap[$api][0]]}{$c->[$ap[$api][1]]}=1;
$cm=max($cm,compose(@{$c}));
This is where the magic happens. The hash $u[0]
lists all (y,z)
coordinate pairs, because we can't have another piece with the same
(y,z)
as an existing one (the rook problem). $u[1]
is (x,z)
and
$u[2]
is (x,y)
.
compose($x,$y,$z)
generates a single integer that increases through
the grid; thus we don't try to generate the same solution in multiple
orders. (If A, B, C is valid, we'll go through A to B to C, but the
path from A directly to C will not later generate B.)
}
}
}
my $d=0;
foreach my $x (0..$n-1) {
foreach my $y (0..$n-1) {
Check for row intersections.
if (exists $u[2]{$x}{$y}) {
next;
}
foreach my $z (0..$n-1) {
if (exists $u[1]{$x}{$z} || exists $u[0]{$y}{$z}) {
next;
}
Ensure we're not backtracking.
if (compose($x,$y,$z)<=$cm) {
next;
}
At this point we have a solution to the rook problem (for the number
of pieces placed so far). Now we check for diagonality.
my @k=(@{$r},[$x,$y,$z]);
OUTER:
foreach my $a (0..$#k-1) {
foreach my $b ($a+1..$#k) {
foreach my $api (0..2) {
my @ax=grep {$_ != $api} (0..2);
my $l=abs($k[$a][$ax[0]]-$k[$b][$ax[0]]);
if ($l ==
abs($k[$a][$ax[1]]-$k[$b][$ax[1]]) &&
$k[$a][$api] == $k[$b][$api]) {
I'm assuming that a queen doesn't generate diagonals in three
dimensions, such that (0,0,0)
and (1,1,1)
are not mutually
attacking. The problem doesn't specify this either way.
@k=();
last OUTER;
}
}
}
}
If we found a new valid solution, put it on the loop buffer.
if (@k) {
$d=1;
push @a,\@k;
}
}
}
}
If we didn't find a solution, this might be a maximum possible number
of pieces. If it's more than we've had before, remember it.
unless ($d) {
my $n=scalar @{$r};
if ($n>$m) {
$m=$n;
$o=$r;
}
}
} while (@a);
Since my pieces are held in the form of a list of coordinates, this
has to be expanded to produce the structure described in the question.
First fill a 3-dimensional array with zeroes.
if (defined $o) {
my @grid;
foreach my $x (0..$n-1) {
my $a;
foreach my $y (0..$n-1) {
my $b;
foreach my $z (0..$n-1) {
push @{$b},0;
}
push @{$a},$b;
}
push @grid,$a;
}
Then put ones in the places where the pieces are.
foreach my $q (@{$o}) {
$grid[$q->[0]][$q->[1]][$q->[2]]=1;
}
print Dumper(\@grid);
}
Finally the composer to order the coordinates.
sub compose {
my ($x,$y,$z)=@_;
return $x*$n*$n+$y*$n+$z;
}
Didn't even look at this in Raku.
Comments on this post are now closed. If you have particular grounds for adding a late comment, comment on a more recent post quoting the URL of this one.