spx2 has asked for the wisdom of the Perl Monks concerning the following question:

hi, I'm parsing documents,each of them has a location
written on them.
There is a slight problem as people write differently
their location.
A friend suggested some time ago using Levenshtein's
algorithm for getting the edit distance between words
if needed.
I used that to establish similarity between words
calculating to know if they belong to a certain class.
So,what I'm trying to do:
Having a list of inconsistent locations,divide them into
equivalence classes choosing an appropriate representant
for each of the classes.
In doing so ,I have used a hash and a similarity function
wich establishes if two words are similar or not like this:

f(w1,w2) return 1 if w1 is contained in w2 return 1 if w2 is contained in w1 return edit_distance(w1,w2)/length(w2) < 0.34 ? 1 : 0
I have a hash whose keys are representants of an equivalence
class and whose values are the classes themselfes.
And after words are classified for a first time. The classes suffer a further re-merging.

Here is the script wich does this.
Here is a information sample on wich it runs.
These are the results I got.
The results are pretty much ok,but they need to be improved.
Questions : are there articles regarding this problem, wich present better solutions? How can I optimise this one ?
Any suggestions are welcome
PS:you can decomment line 92 to make it not process all the file and run faster.
EDIT:
corrected problem in the f function above instead
of > it's <

Replies are listed 'Best First'.
Re: separating text words in similarity classes using levenshtein
by eric256 (Parson) on Dec 10, 2007 at 22:04 UTC

    If I were to do this, instead of using a hash and naming the clasess, I would use an array of classes where each array is a class. Then to add works you look at each class and and the word to the first class where the average distance from the new word to each word in the collection is below a certain threshold. That is what I've done below. To make it even better you could scan every class and add it to the class that has the lowest average distance. I also only added the word once, and if its already added skipped it.

    #!/usr/bin/perl use strict; use warnings; use Text::Levenshtein qw/fastdistance/; use Data::Dumper; my $classes = []; my $index = {}; my $limit = 0.5; sub avg_distance { my ($new_word, $class) = @_; my $distance = 0; for my $word (@$class) { $distance += fastdistance($new_word, $word); } return $distance / @$class; } sub put_or_create { my ($w) = @_; $w =~ s/[^a-z]//g; my $found=0; return if exists $index->{$w}; foreach my $class ( @$classes ) { if((avg_distance($w,$class)/length($w)) < $limit) { push @$class, $w; $index->{$w} = $class; $found = 1; last; }; }; if(! $found) { my $class = [$w]; push @$classes, $class; $index->{$w} = $class; }; }; #watch for an error opening the file open( my $file,"<", "possible_locations.txt") or die "Failed to open f +ile: $!"; my @locations = <$file>; #remove the line endings chomp(@locations); my $count = 0; for (@locations) { #last if ++$count/@locations > 0.2;#not all put_or_create($_) } for (@$classes) { print join(",", sort @$_), "\n"; }

    This approaches might need some improvement. The first that comes to mind is add the word to the class/set that is closest, then divide that set up when the average distance becomes too great. I might even give that a shot as it sounds fun!


    ___________
    Eric Hodges
      thanks.
      it's much better than mine
      4m30s vs. 14m(I suspect it's because all that hash look-ups I make use of)
      but it's not putting the real words in the classes
      it's putting the words modified by that regex wich
      strips off unwanted characters.
      altough there still may remain cases like:
      when one writes this : アレの i るs オ i
      wich at a closer look is "Ploiesti" wich is a city
      in Romania but it is interpreted as Iasi(wich is another city in Romania) just because the edit-distance is close
      but I agree that this is a very special case
      also Caracas is a city in Venezuela and it has edit-distance
      very close to Caracal wich is a small city in Romania
      Should I check first against a list of cities ?
      I also found this list of all cities,regions and countries in the world
      and I'm wondering if it would be a good
      Ideea to parse those(they're csv I guess),get some tables in the
      database with them and check against them when a new
      entry is beein added ... but that could take some time
Re: separating text words in similarity classes using levenshtein
by ian (Beadle) on Dec 10, 2007 at 17:41 UTC
    I was not able to see any results after following your link.

    Taking a quick look at your data, I might suggest using regexes and Regexp::Assemble. Perl Hacks demonstrates how to use Regexp::Assemble to build a dispatch table that you may find helpful.

    -- Ian Tegebo
      thank you for the suggestion,actually I needed that ,but in a different part of the project.It's very useful. I'm not sure how to apply it here
      I've just verified the links and they work. Please try again

        The results link doesn't work for me either, well it works but its completly blank here.

        It would be much better to provide some small samples embeded in your code here. That way if someone else comes along next year all the data will still be here.


        ___________
        Eric Hodges