This post extends that post in 2 ways. First, we create a slightly harder problem so that we can see some more XML::Twig code. Then I engage in a long-winded tangent about Perl and problem-solving engines.
Now we want to find which cities are 2-away from menlo park. Or put another way, those which are adjacent to palo alto and atherton. In our case, we want to omit backtracking on our path. So even though menlo park is in fact 2-away from menlo park (just go to atherton and come back), we will omit these cases.
Here is a program which solves this problem:
use XML::Twig; my $t = XML::Twig->new(PrettyPrint => 'record'); $t->parsefile('adj.xml'); my $root = $t->root; my @pair = $root->children; my $city = 'menlo park'; sub candidate_generator { my ($search_text, @data) = @_; grep { grep { $_->text eq $search_text } $_->children } @data; } my @adj = candidate_generator($city,@pair); my @next_city = map { $_->first_child->text ne $city ? $_->first_child->text : $_->last_child->text } @adj; print "cities next to $city == @next_city"; # -------- no changes up to here. Here is the new stuff: # now we use a slightly different map and grep my @next_to_next = grep { $_->first_child->text ne $city and $_->last_child->text ne $city } map { candidate_generator($_, @pair) } @next_city; map { $_->print } @next_to_next;
If you followed the first program, then this one should really require no explanation other than an analysis of the efficiency of our means of calculating 2-away cities:
Because we map() then grep(), we are doing what is known as generate-and-test: we create a bunch of candidates and then later filter them out based on a criteria.my @next_to_next = grep { $_->first_child->text ne $city and $_->last_child->text ne $city } map { candidate_generator($_, @pair) } @next_city;
$_->delete for (@adj); my @next_to_next = map { candidate_generator($_, @pair) } @next_city; map { $_->print } @next_to_next;
So here we prune the XML-tree of the records which were 1-away from our target city and then our candidate generator can only generate the correct candidates.
But actually this change in the program is more than just efficient. We can simply run this loop N times when we want to find the cities which are N+1 hops away.
But this is the beauty of using this approach, we can cut away the search space with a single call to delete(). We could also do all sorts of other funky things with our tree just as easily...
So far, I have tackled this problem the Perl way - I dove right in and didn't quit coding until I was done. However, I would like to propose another way to handle this problem, and that way is what is known as forward-chaining. A very old, very stable forward-chaining rules-based system is CLIPS.
Forward chaining is where you start with a set of facts and then you build a bunch of rules which fire if their preconditions meet some of the facts on the stack. Typically when these rules fire, they assert new facts and retract some old ones. And then some other rules notice these new asserted facts on the stack and then go through the same assert/retract cycle until finally some rule notices that you have reached your goal and announces it and terminates the program.
This is opposite of a backward-chaining system like Prolog, where you start with your goal and attempt to find rules which match the goal and search for facts with satisfy these rules.
It seems that for any problem domain, decoupling this from that seems to provide great benefits at some point. For CLIPS, time has shown that decoupling a problem solver into the following things works well:
And oftentimes in Perl programs, the guiding logic is not necessarily extracted out and placed somewhere. Instead you might see a policy for a system in an if-then somewhere:
if ($file !~ /p(\d{6,7})_(\w+)_(\w+).zip/) { then die "$file violates the companies syntax for patch filenames"; }
However, as mentioned in the book "Data Munging with Perl" by Dave Cross, it is much better to place your business logic in a module to facilitate re-use and also so that all system logic can be edited in a single place:
System::Logic::valid_patchfile(File => $file, DieOnErr => 1);
The big win with this type of program is that you don't have to run map() or grep() as I have been doing but instead simply code up a bunch of program-state-recognizers. Also, you never have to write the rule selector. But all in all, until something like that shows its necessity I will just keep solving problems in the most intuitive way possible.
Edit: chipmunk 2001-08-20
|
---|
Replies are listed 'Best First'. | |
---|---|
Re: (don't stop) Adjacency List Processing Continued + Expert System Discussion
by mrmick (Curate) on Aug 20, 2001 at 18:02 UTC |