web-dev-qa-db-fra.com

Existe-t-il un programme de correspondance de chaîne fuzzy, qui fournit un score de correspondance?

J'ai la liste des chaînes dans le fichier A et le fichier B. Je veux prendre chaque chaîne dans le fichier A et trouver la chaîne la plus similaire dans le fichier B.

Pour cela, je recherche un outil permettant une comparaison floue.

par exemple:

$ fuzzy_compare "Some string" "Some string"
100

Où 100 est un certain ratio d'égalité. Par exemple distance de Levenshtein .

Y a-t-il une utilité? Je ne veux pas réinventer la roue.

17
c0rp

J'ai trouvé cette page qui fournit des implémentations de l'algorithme de distance Levenshtein dans différentes langues. Ainsi, par exemple, à bash, vous pourriez faire:

#!/bin/bash
function levenshtein {
    if [ "$#" -ne "2" ]; then
        echo "Usage: $0 Word1 Word2" >&2
    Elif [ "${#1}" -lt "${#2}" ]; then
        levenshtein "$2" "$1"
    else
        local str1len=$((${#1}))
        local str2len=$((${#2}))
        local d i j
        for i in $(seq 0 $(((str1len+1)*(str2len+1)))); do
            d[i]=0
        done
        for i in $(seq 0 $((str1len))); do
            d[$((i+0*str1len))]=$i
        done
        for j in $(seq 0 $((str2len))); do
            d[$((0+j*(str1len+1)))]=$j
        done

        for j in $(seq 1 $((str2len))); do
            for i in $(seq 1 $((str1len))); do
                [ "${1:i-1:1}" = "${2:j-1:1}" ] && local cost=0 || local cost=1
                local del=$((d[(i-1)+str1len*j]+1))
                local ins=$((d[i+str1len*(j-1)]+1))
                local alt=$((d[(i-1)+str1len*(j-1)]+cost))
                d[i+str1len*j]=$(echo -e "$del\n$ins\n$alt" | sort -n | head -1)
            done
        done
        echo ${d[str1len+str1len*(str2len)]}
    fi
}

while read str1; do
        while read str2; do
                lev=$(levenshtein "$str1" "$str2");
                printf '%s / %s : %s\n' "$str1" "$str2" "$lev"
        done < "$2"
done < "$1"

Enregistrez-le sous le nom ~/bin/levenshtein.sh, rendez-le exécutable (chmod a+x ~/bin/levenshtein.sh) et exécutez-le sur vos deux fichiers. Par exemple:

$ cat fileA
foo
Zoo
bar
fob
baar
$ cat fileB
foo
loo
baar
bob
gaf
$ a.sh fileA fileB
foo / foo : 0
foo / loo : 1
foo / baar : 4
foo / bob : 2
foo / gaf : 3
Zoo / foo : 1
Zoo / loo : 1
Zoo / baar : 4
Zoo / bob : 2
Zoo / gaf : 3
bar / foo : 3
bar / loo : 3
bar / baar : 1
bar / bob : 2
bar / gaf : 2
fob / foo : 1
fob / loo : 2
fob / baar : 4
fob / bob : 1
fob / gaf : 3
baar / foo : 4
baar / loo : 4
baar / baar : 0
baar / bob : 3
baar / gaf : 3

C'est bon pour quelques modèles, mais cela va être très lent pour les fichiers plus volumineux. Si cela vous pose problème, essayez l’une des implémentations dans d’autres langues. Par exemple Perl:

#!/usr/bin/Perl 
use List::Util qw(min);

sub levenshtein
{
    my ($str1, $str2) = @_;
    my @ar1 = split //, $str1;
    my @ar2 = split //, $str2;

    my @dist;
    $dist[$_][0] = $_ foreach (0 .. @ar1);
    $dist[0][$_] = $_ foreach (0 .. @ar2);

    foreach my $i (1 .. @ar1) {
        foreach my $j (1 .. @ar2) {
            my $cost = $ar1[$i - 1] eq $ar2[$j - 1] ? 0 : 1;
            $dist[$i][$j] = min(
                            $dist[$i - 1][$j] + 1, 
                            $dist[$i][$j - 1] + 1, 
                            $dist[$i - 1][$j - 1] + $cost
                             );
        }
    }

    return $dist[@ar1][@ar2];
}
open(my $fh1, "$ARGV[0]");
open(my $fh2, "$ARGV[1]");
chomp(my @strings1=<$fh1>);
chomp(my @strings2=<$fh2>);

foreach my $str1 (@strings1) {
    foreach my $str2 (@strings2) {
        my $lev=levenshtein($str1, $str2);
        print "$str1 / $str2 : $lev\n";
    }
}

Comme ci-dessus, enregistrez le script sous ~/bin/levenshtein.pl, rendez-le exécutable et exécutez-le avec les deux fichiers comme arguments:

~/bin/levenstein.pl fileA fileB

Même dans les très petits fichiers utilisés ici, l'approche Perl est 10 fois plus rapide que celle bash:

$ time levenshtein.sh fileA fileB > /dev/null

real    0m0.965s
user    0m0.070s
sys     0m0.057s

$ time levenshtein.pl fileA fileB > /dev/null
real    0m0.011s
user    0m0.010s
sys     0m0.000s
23
terdon