是否有任何用于模糊字符串匹配的程序来提供匹配分数?


17

我在file A和file 中有字符串列表B。我想获取文件A中的每个字符串,然后找到文件B中最相似的字符串。

为此,我正在寻找一种提供模糊比较的工具。

例如:

$ fuzzy_compare "Some string" "Some string"
100

其中100是一些平等比率。例如Levenshtein距离

有没有实用程序?我不想重新发明轮子。


1
我编辑了您的问题以提高清晰度,但将其更改为询问将fileA中的每个字符串与fileB中的字符串(不仅是第一个字符串)进行比较。我以为那是您的意思,如果我错了,请纠正我。
terdon '16


@muru不,这仅用于模糊匹配,OP需要一个分数。
terdon '16

Answers:


23

我发现此页面以不同的语言提供了Levenshtein距离算法的实现。因此,例如在bash中,您可以执行以下操作:

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

将其另存为~/bin/levenshtein.sh,使其可执行(chmod a+x ~/bin/levenshtein.sh)并在您的两个文件上运行它。例如:

$ 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

对于某些模式来说这很好,但是对于较大的文件将变得非常慢。如果这是一个问题,请尝试使用其他语言的实现之一。例如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";
    }
}

如上所述,将脚本另存为~/bin/levenshtein.pl,使其可执行,并使用两个文件作为参数运行它:

~/bin/levenstein.pl fileA fileB

即使在这里使用的非常小的文件中,Perl方法也比bash方法快10倍:

$ 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

要对结果进行更多说明:引用维基百科,两个单词之间的Levenshtein距离是将一个单词转换为另一个单词所需的最小单字符编辑(即,插入,删除或替换)次数。这意味着数字越小,匹配度越高。零表示完美匹配。另请注意,Levenshtein距离可平等处理每个字符,这意味着“ foo”和“ Foo”导致的距离与“ foo”和“ fox”相同。
scai 2016年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.