[SPOILER] Solution to Perl Quiz of the Week #26 (Acrostic puzzle formatter)
Roger Burton West <roger-UvLOT2mcgw/[email protected]> Tue, 19 Oct 2004 15:24:07 +0100
| Newsgroups | gmane.comp.lang.perl.qotw.discuss |
|---|---|
| Message-ID | <[email protected]> |
On Thu, Oct 14, 2004 at 04:49:48PM -0400, Mark Jason Dominus wrote:
>This week, you'll write a program to format and print puzzles. The
>input to the program will be a file with two sections. The first
>section will be the quotation, and will be followed by a single blank
>line, and then the second section, which will be the list of answer
>words, one per line.
There are two parts to this puzzle I found technically interesting: the
wrapping of an output which is itself multiline over multiple "large"
lines, which I performed with an output line cache, and the spread of
answer letters among different clues.
The latter is done with a weighting system. Each clue-word has a
weight, initially 0; when a letter is chosen from it, the weight is
increased by N (equal to the number of clue-words). For a word to be
used, a random number from 0 to N must exceed the word's current
weight. If after ten selections no candidate word has been found, all
non-zero weights are decreased by 1.
#! /usr/bin/perl -w
use strict;
my $outputwidth=($ENV{COLUMNS} || 80) - 8;
my $quotation='';
my @words;
my $state=0;
while (<>) {
chomp;
if ($_ eq '') {
$state=1;
} else {
$_=lc($_);
if ($state==0) {
$quotation.=" $_";
} else {
push @words,$_;
}
}
}
$quotation =~ s/[^a-z ]//g;
$quotation =~ s/\s{2,}/ /g;
my @quotation=split ' ',$quotation;
my %letters;
my %locations;
foreach my $wn (0..$#words) {
my @letters=split '',$words[$wn];
foreach my $ln (0..$#letters) {
my $letter=$letters[$ln];
push @{$letters{$letter}},$wn;
push @{$locations{$letter}},$ln;
}
}
my @wordlabels;
{
my $nn=1;
while (scalar @wordlabels < scalar @words) {
push @wordlabels, map {$_ x $nn} ('A'..'Z');
}
}
my $n=1;
my @output;
my @wt;
my @weights=(0) x scalar @words;
foreach my $qword (@quotation) {
my @ow;
foreach my $letter (split '',$qword) {
my $own;
my $wn;
do {
my $tick=10;
do {
$own=int(rand(scalar @{$letters{$letter}}));
$wn=$letters{$letter}[$own];
$tick--;
} while (rand(scalar @words) <= $weights[$wn] && $tick);
if ($tick==0) {
map {($weights[$_]==0)?0:$weights[$_]--} (0..$#weights);
}
} while (rand(scalar @words) <= $weights[$wn]);
$weights[$wn]+=scalar @words;
splice @{$letters{$letter}},$own,1;
my $ln=splice @{$locations{$letter}},$own,1;
push @ow,$n.$wordlabels[$wn];
$wt[$wn][$ln]=$n;
$n++;
}
push @output,\@ow;
}
my $maxlen=length($n.$wordlabels[-1]);
my @outlines=('','');
foreach my $wn (0..$#output+1) {
my @str=('','');
if ($wn <= $#output) {
my $word=$output[$wn];
$str[0]=join(' ',('_' x $maxlen) x scalar @{$word});
$str[1]=join(' ',map {sprintf("%-${maxlen}s",$_)} @{$word});
}
my $len=length($outlines[0])+length($str[0])+$maxlen+2;
if ($len > $outputwidth || $wn > $#output) {
print "$outlines[0]\n$outlines[1]\n\n\n";
@outlines=@str;
} else {
if ($outlines[0]) {
map {$outlines[$_].=' ' x (2+$maxlen)} (0,1);
}
map {$outlines[$_].=$str[$_]} (0,1);
}
}
foreach my $clue (0..$#wt) {
printf('%'.length($wordlabels[-1]).'s. ',$wordlabels[$clue]);
print join ' ',map{'_' x $maxlen} (1..length($words[$clue]));
print "\n";
print ' ' x (length($wordlabels[-1])+2);
print join ' ',map {sprintf("%-${maxlen}s",$_)} @{$wt[$clue]};
print "\n\n";
}