[SPOILER] Solution to Perl 'Easy' Quiz #2005-2
Brad Greenlee <[email protected]> Sat, 05 Feb 2005 09:10:16 -0800
| Newsgroups | gmane.comp.lang.perl.qotw.discuss |
|---|---|
| Message-ID | <[email protected]> |
As a number of you did, I included my own test harness. I only made two
changes since the answers started rolling in, both on the test harness
(stole Rod Adams' nifty code for finding subs, and added a few test
cases I hadn't thought of).
Nonstandard modules used: IO::Scalar (t_ioscalar), Inline (t_inline_c),
and Inline::Python (t_inline_py)
I had a lot of fun writing the Inline::C version. It's been ages since
I've done anything in C. There's also an Inline::Python version.
Here are the benchmarks on my subs:
ioscalar 6682/s
regex3 87764/s
chop1 88407/s
inline_py 92773/s
split1 113121/s
regex2 116378/s
regex1 243900/s
rindex1 318317/s
inline_c 784720/s
Cheers,
-b
#!perl -w
use strict;
no strict 'refs';
use Benchmark;
my %test = ('this.is.a.file.txt' => 'thisisafile.txt',
'thisisafiletxt' => 'thisisafiletxt',
'thisisafile.txt' => 'thisisafile.txt',
'.thisisafile.txt' => 'thisisafile.txt',
'.thisisafiletxt' => '.thisisafiletxt',
'thisisafile.txt.' => 'thisisafiletxt.',
'thisisafiletxt.' => 'thisisafiletxt.',
'thisisafiletxt..' => 'thisisafiletxt.',
'...this..is.a.file..txt' => 'thisisafile.txt',
'' => '',
'.' => '.',
'..' => '.',
);
# run tests on all subs starting with 't_'
my $failed = 0;
foreach my $str (keys %test) {
my $orig_str = $str;
for my $name (sort keys %::) {
next unless *{$name}{CODE} && substr($name,0,2) eq 't_';
my $result = *{$name}{CODE}->($str);
if ($result ne $test{$str}) {
print "$name failed:\n";
print " test string: [$str]\n";
print " expected: [$test{$str}]\n";
print " result: [$result]\n";
$failed++;
}
if ($str ne $orig_str) {
print "$name failed: input string modified\n";
print " original string: [$orig_str]\n";
print " modified to: [$str]\n";
$failed++;
}
}
}
print "Tests failed...not running benchmarks\n" && exit if $failed;
# run benchmarks
my $str = 'this.is.a.file.txt';
Benchmark::cmpthese(-5, {
'regex1' => "t_regex1('$str')",
'regex2' => "t_regex2('$str')",
'regex3' => "t_regex3('$str')",
'rindex1' => "t_rindex1('$str')",
'split1' => "t_split1('$str')",
'chop1' => "t_chop1('$str')",
'ioscalar' => "t_ioscalar('$str')",
'inline_c' => "t_inline_c('$str')",
'inline_py' => "t_inline_py('$str')",
});
sub t_regex1 {
$_ = $_[0];
s/\.(?=.*?\..*)//g;
return $_;
}
sub t_regex2 {
$_ = $_[0];
my $str = '';
$str .= $1 while m/(.*?)\./gc;
return $_ if !defined(pos);
return m/\G(.*)/gcs ? "$str.$1" : $str;
}
sub t_regex3 {
$_ = $_[0];
my @parts = ();
push @parts, $1 while m/(.*?)\./gc;
return $_ if !defined(pos);
return m/\G(.*)/gcs ? join('',@parts,".$1") : join('',@parts);
}
sub t_rindex1 {
my $prefix = substr $_[0], 0, rindex $_[0], '.';
$prefix =~ s/\.//g;
return $prefix . substr $_[0], rindex $_[0], '.';
}
sub t_split1 {
# major kludge to handle trailing dots
my $trailing_dot = $_[0] =~ m/(\.+)$/;
my @parts = split /\./, $_[0];
if ($trailing_dot) {
return join('',@parts,'.');
}
return @parts > 1 ? join('',@parts[0..$#parts-1],".$parts[-1]") :
join('',@parts);
}
sub t_chop1 {
$_ = shift;
my $str = '';
my $c;
$str .= $c while ($c = chop) && $c ne '.';
$str .= $c if $c;
while ($c = chop) {
next if $c eq '.';
$str .= $c;
}
return scalar reverse $str;
}
sub t_ioscalar {
my ($str,$lastline) = ('','');
use IO::Scalar;
$/ = '.';
my $in_fh = new IO::Scalar \$_[0];
my $out_fh = tie *OUT, 'IO::Scalar', \$str;
my $trailing_dot = 0;
while (<$in_fh>) {
$trailing_dot = chomp;
print OUT $lastline;
$lastline = $_;
}
print OUT '.' if length($lastline) < length($_[0]) && !$trailing_dot;
print OUT $lastline;
print OUT '.' if $trailing_dot;
return $str;
}
sub t_inline_c {
use Inline C => <<'[END]';
char* rmdots_c(char* str) {
char* newstr = malloc(strlen(str));
/* find rightmost dot in string */
int i = 0;
int j = 0;
int rdot = -1;
int numdots = 0;
while (str[i]) {
if (str[i] == '.') {
rdot = i;
numdots++;
}
else {
newstr[j++] = str[i];
}
i++;
}
if (!numdots) { return str; } /* no dots, so we're done */
/* copy string from rightmost dot on */
strcpy(newstr + rdot - (numdots - 1), str + rdot);
return newstr;
/* or we could play nice and do this, which is slower:
return realloc(newstr, j);
*/
}
[END]
return rmdots_c($_[0]);
}
sub t_inline_py {
use Inline Python => <<'[END]';
def rmdots_py(str):
rdot = str.rfind('.')
if rdot == -1: return str # no dots #
return str[:rdot].replace('.','') + str[rdot:]
[END]
return rmdots_py($_[0]);
}