Ieronim,
I truly like the elegance of my previous solution. It is Concise, straight forward, and fast. You can even remove the build_db() after the database has been compiled as I have shown below in about 20 lines of code:
#!/usr/bin/perl
use strict;
use warnings;
use Storable;
my ($src, $tgt) = @ARGV;
die "Usage: $0 <src> <tgt>" if ! defined $src || ! defined $tgt;
die "The <src> and <tgt> must be same length" if length($src) != lengt
+h($tgt);
my $db = retrieve('dictionary.db');
my $path = find_path($src, $tgt, $db->{length($tgt)});
print "$path\n";
sub find_path {
my ($src, $tgt, $list, $seen, $work) = @_;
@$work = map {key => $_ => path => "$src->$_"}, @{$list->{$src}} i
+f ! defined $work;
my $next = [];
for (@$work) {
my ($word, $path) = @{$_}{qw/key path/};
next if $seen->{$word}++;
return $path if $word eq $tgt;
push @$next, map {key => $_, path => "$path->$_"}, @{$list->{$
+word}};
}
return find_path($src, $tgt, $list, $seen, $next) if @$next;
return 'path not found';
}
For the sake of completeness, I am providing the following version which allows you to use non-dictionary words at the endpoints.
#!/usr/bin/perl
use strict;
use warnings;
use Storable;
use Text::LevenshteinXS 'distance';
my ($src, $tgt) = @ARGV;
die "Usage: $0 <src> <tgt>" if ! defined $src || ! defined $tgt;
die "The <src> and <tgt> must be same length" if length($src) != lengt
+h($tgt);
my $db = -e 'dictionary.db' ? retrieve('dictionary.db') : build_db();
my $list = check_db($src, $tgt, $db);
die $list if ! ref $list;
my $path = find_path($src, $tgt, $list);
print "$path\n";
sub find_path {
my ($src, $tgt, $list, $seen, $work) = @_;
@$work = map {key => $_ => path => "$src->$_"}, @{$list->{$src}} i
+f ! defined $work;
my $next = [];
for (@$work) {
my ($word, $path) = @{$_}{qw/key path/};
next if $seen->{$word}++;
return $path if $word eq $tgt;
push @$next, map {key => $_, path => "$path->$_"}, @{$list->{$
+word}};
}
return find_path($src, $tgt, $list, $seen, $next) if @$next;
return 'path not found';
}
sub check_db {
my ($src, $tgt, $db) = @_;
my $len = length($tgt);
return "Unable to work with $tgt" if ! exists $db->{$len};
for my $word ($src, $tgt) {
if (! exists $db->{$len}{$word}) {
for (grep distance($_, $word) == 1, keys %{$db->{$len}}) {
push @{$db->{$len}{$word}}, $_;
push @{$db->{$len}{$_}}, $word;
}
return "Unable to work with $word" if ! @{$db->{$len}{$wor
+d}};
}
}
return $db->{$len};
}
# Runs once and can be removed after DB is built
sub build_db {
open (my $dict, '<', '2of12.txt') or die "Unable to open '2of12.tx
+t' for reading: $!";
my ($db, %data);
while (<$dict>) {
chomp;
push @{$data{length()}}, $_;
}
for my $len (keys %data) {
my $end = $#{$data{$len}};
for my $i (0 .. $end - 1) {
my $word = $data{$len}[$i];
for my $j ($i + 1 .. $end) {
my $test = $data{$len}[$j];
! exists $db->{$len}{$_} and $db->{$len}{$_} = [] for
+$word, $test;
if (distance($word, $test) == 1) {
push @{$db->{$len}{$word}}, $test;
push @{$db->{$len}{$test}}, $word;
}
}
}
}
store $db, 'dictionary.db';
return retrieve('dictionary.db');
}
Feel free to adjust this as you see fit.