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.
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