[SPOILER] Re: Perl 'Easy' Quiz #2005-2 (Perl and PostScript)
Roger Burton West <roger-UvLOT2mcgw/[email protected]> Sun, 6 Feb 2005 11:51:52 +0000
| Newsgroups | gmane.comp.lang.perl.qotw.discuss |
|---|---|
| Message-ID | <[email protected]> |
On Wed, Feb 02, 2005 at 07:54:19PM +0200, Shlomi Fish wrote:
>I've been on FreeNode's #perl channel one day when someone asked how to remove
>all periods from a string except the last one. So for example
>"this.is.a.file.txt" will become "thisisafile.txt".
>
>Your mission is to write as many different solutions as possible to this
>problem. You should write functions which will accept a single scalar
>argument containing the string, and return a single scalar containing the
>modified string.
I wrote test harnesses first. Test strings are in the file "in", and
the expected output is in "out", both one per line. My test cases were:
foo
foo.bar
foo.bar.baz
foo.bar.baz.qux
foo.
.foo
foo.bar.
.foo.bar
foo..bar
foo...bar
foo..bar.baz
foo.bar..baz
expecting the output:
foo
foo.bar
foobar.baz
foobarbaz.qux
foo.
.foo
foobar.
foo.bar
foo.bar
foo.bar
foobar.baz
foobar.baz
The Perl test harness is simple (stolen from one of MJD's a while ago;
it takes the file as a parameter, and expects it to contain a
"dot_remove" function and return true):
#! /usr/bin/perl -w
use strict;
my $prog = shift or die "Usage: $0 libfile.pl\n";
do $prog;
die "Couldn't find 'dot_remove()'\n"
unless defined(&dot_remove);
open I,'<in';
open O,'<out';
my $fails=0;
while (<I>) {
chomp;
my $o=&dot_remove($_);
chomp (my $o2=<O>);
unless ($o eq $o2) {
print "$_: expected $o2, got $o\n";
$fails++;
}
}
unless ($fails) {
print "OK\n";
}
My first Perl solution is straight out of the perlop manpage:
# while_re.pl
sub dot_remove {
my ($a)=@_;
1 while $a =~ s/([^.]*)\.([^.]*)\./$1$2./g;
return $a;
}
The second is slightly odder:
# reverse_re.pl
sub dot_remove {
my ($a)=@_;
my $d=reverse $a;
if (my ($b,$c) = ($d =~ /^([^.]*)\.(.*)/)) {
$c =~ s/\.//g;
$a=reverse("$b.$c");
}
return $a;
}
The third was an effort to avoid using the regex engine at all (though
I'm not sure whether split '' counts):
# hash.pl
sub dot_remove {
my ($a)=@_;
my @a=split '',$a;
my %h;
map {$h{$_}++} @a;
foreach (2..($h{'.'} || 0)) {
my $p=index $a,'.';
splice @a,$p,1;
$a=join('',@a);
}
return $a;
}
Going even further:
# index.pl
sub dot_remove {
my ($a)=@_;
my $p=index($a,'.');
while ($p>-1 &&
index($a,'.',$p+1) > -1) {
substr($a,$p,1)='';
$p=index($a,'.');
}
return $a;
}
Then I started using PostScript (because I enjoy it and for some reason
there's no PostScript QOTW). Again, the first stage is the test
harness. I haven't yet found a way to pass a command-line parameter to
a PostScript program running under GhostScript, so you'll have to
change the included filename manually:
%!
(counter.ps) run
/INPUT (in) (r) file def
/VERIFY (out) (r) file def
/Helvetica findfont
12 scalefont
setfont
0.5 72 mul 10 72 mul translate
{
INPUT 800 string readline
{
} {
exit
} ifelse
dup
remove_dots
dup
2.5 72 mul 0 moveto
show
5 72 mul 0 moveto
VERIFY 800 string readline pop
eq {
(OK)
} {
(failed)
} ifelse
show
0 0 moveto
show
0 0.5 neg 72 mul translate
} loop
INPUT closefile
VERIFY closefile
(I submit that my unthinking renaming of what in Perl was the
"dot_remove" function to "remove_dots" shows that I was starting to get
into the PostScript mindset. Or something.)
First pass at the postscript function, still quite traditionally
programmed:
%! counter.ps
%% string1 remove_dots string2
% Function: removes all but last . in a string
/remove_dots {
dup
/COUNT 0 def
{
46 eq {
/COUNT COUNT 1 add def
} if
} forall
COUNT 1 gt {
dup
length COUNT sub 1 add string
/OUT exch def
/POS 0 def
{
dup
46 eq {
COUNT 1 gt {
pop
-1
/COUNT COUNT 1 sub def
} if
} if
dup
-1 ne {
OUT exch POS exch put
/POS POS 1 add def
} {
pop
} ifelse
} forall
OUT
} if
} bind def
Second attempt, a little neater - and thanks to the PostScript FAQ's
string appender, done entirely without named variables:
%! search.ps
%% string1 remove_dots string2
% Function: removes all but last . in a string
/remove_dots {
mark exch
{
(.) search { % post match pre
exch pop exch % pre post
} {
exit
} ifelse
} loop
counttomark 1 gt {
(.) exch
} if
counttomark 1 sub {
append
} repeat
exch pop
} bind def
%% string1 string2 append string
% Function: Concatenates two strings together.
% from http://www.postscript.org/FAQs/language/node68.html
/append {
2 copy length exch length add % find the length of the new.
string dup % string1 string2 string string
4 2 roll % string string string1 string2
2 index 0 3 index
% string string string1 string2 string 0 string1
putinterval % stuff the first string in.
% string string string1 string2
exch length exch putinterval
} bind def
Roger