Gibt es ein Programm für den Fuzzy-String-Abgleich, das eine Übereinstimmungsbewertung liefert?


17

Ich habe eine Liste von Zeichenfolgen in Datei Aund Datei B. Ich möchte jeden String in Datei A nehmen und den ähnlichsten String in Datei B finden.

Dafür suche ich ein Tool, das unscharfe Vergleiche ermöglicht.

beispielsweise:

$ fuzzy_compare "Some string" "Some string"
100

Wobei 100 ein Gleichheitsverhältnis ist. Zum Beispiel Levenshtein Abstand .

Gibt es ein Dienstprogramm? Ich möchte das Rad nicht neu erfinden.


1
Ich habe Ihre Frage bearbeitet, um die Übersichtlichkeit zu verbessern, sie jedoch dahingehend geändert, dass die einzelnen Zeichenfolgen in Datei A mit denen in Datei B verglichen werden und nicht nur die erste. Ich nahm an, das war es, was Sie meinten, aber bitte korrigieren Sie mich, wenn ich falsch lag.
Terdon


@muru nein, das ist nur für Fuzzy Matching, das OP braucht eine Punktzahl.
Terdon

Antworten:


23

Ich habe diese Seite gefunden, die Implementierungen des Levenshtein-Entfernungsalgorithmus in verschiedenen Sprachen enthält. Zum Beispiel könnten Sie in bash Folgendes tun:

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

Speichern Sie das als ~/bin/levenshtein.sh, machen Sie es ausführbar ( chmod a+x ~/bin/levenshtein.sh) und führen Sie es auf Ihren zwei Dateien aus. Beispielsweise:

$ 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

Das ist für einige Muster in Ordnung, wird aber für größere Dateien sehr langsam. Wenn dies ein Problem ist, versuchen Sie eine der Implementierungen in anderen Sprachen. Zum Beispiel 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";
    }
}

Speichern Sie das Skript wie oben, ~/bin/levenshtein.plmachen Sie es ausführbar und führen Sie es mit den beiden Dateien als Argumente aus:

~/bin/levenstein.pl fileA fileB

Selbst in den hier verwendeten sehr kleinen Dateien ist der Perl-Ansatz zehnmal schneller als der Bash-Ansatz:

$ 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

Um weitere Erklärungen zu den Ergebnissen hinzuzufügen: In Wikipedia ist der Levenshtein-Abstand zwischen zwei Wörtern die minimale Anzahl von Einzelzeichen-Änderungen (dh Einfügungen, Löschungen oder Ersetzungen), die erforderlich sind, um ein Wort in das andere zu ändern . Das heißt, je niedriger die Zahl, desto besser die Übereinstimmung. Eine Zahl von Null bedeutet eine perfekte Übereinstimmung. Beachten Sie auch, dass der Levenshtein-Abstand alle Zeichenbearbeitungen gleich behandelt, was bedeutet, dass "foo" und "Foo" zu demselben Abstand führen wie "foo" und "fox".
Scai
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.