Esiste un programma per la corrispondenza delle stringhe fuzzy, che fornisce un punteggio di corrispondenza?


17

Ho un elenco di stringhe in file Ae file B. Voglio prendere ogni stringa nel file A e trovare la stringa più simile nel file B.

Per questo, sto cercando uno strumento che offra un confronto sfocato.

per esempio:

$ fuzzy_compare "Some string" "Some string"
100

Dove 100 è un certo rapporto di uguaglianza. Ad esempio la distanza di Levenshtein .

C'è qualche utilità? Non voglio reinventare la ruota.


1
Ho modificato la tua domanda per migliorare la chiarezza, ma l'ho modificata per chiedere di confrontare ogni stringa nel file A con quelle nel file B e non solo la prima. Pensavo fosse quello che volevi dire, ma per favore correggimi se mi sbagliavo.
Terdon,


@muru no, è solo per la corrispondenza fuzzy, l'OP ha bisogno di un punteggio.
terdon,

Risposte:


23

Ho trovato questa pagina che fornisce implementazioni dell'algoritmo di distanza Levenshtein in diverse lingue. Quindi, ad esempio in bash, potresti fare:

#!/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"

Salvalo come ~/bin/levenshtein.sh, rendilo eseguibile ( chmod a+x ~/bin/levenshtein.sh) ed eseguilo sui tuoi due file. Per esempio:

$ 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

Va bene per alcuni schemi ma diventerà molto lento per file più grandi. Se questo è un problema, prova una delle implementazioni in altre lingue. Ad esempio 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";
    }
}

Come sopra, salva lo script come ~/bin/levenshtein.ple rendilo eseguibile ed eseguilo con i due file come argomenti:

~/bin/levenstein.pl fileA fileB

Anche nei file molto piccoli usati qui, l'approccio Perl è 10 volte più veloce di quello 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

Per aggiungere qualche spiegazione in più sui risultati: citando wikipedia, la distanza di Levenshtein tra due parole è il numero minimo di modifiche a carattere singolo (ovvero inserzioni, cancellazioni o sostituzioni) necessarie per cambiare una parola nell'altra . Ciò significa che più basso è il numero, migliore è la corrispondenza. Un numero zero indica una corrispondenza perfetta. Nota anche che la distanza di Levenshtein gestisce le modifiche di ogni personaggio allo stesso modo, il che significa "foo" e "Foo" portano alla stessa distanza di "foo" e "fox".
scai,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.