我在文本文件中有数十万devnagari单词(每行一个字)。为了更正,我不得不在另一个文件中复制类似于“अक्तूबर,अक्टूबर”“कौम,क़ौम”的最相似的单词。拷贝允许最大两位差。为此,我使用了"awk“来查找单词的差异,并将类似的单词复制到另一个文件中。但是它失败了,因为这个命令只适用于罗马字符,而不适用于devnagari字符。
awk -v string=कौम -v string1=क़ौम '{ for (i=1;i<=length(string);i++) { if (substr(string,i,1) != substr(string1,i,1)) { count++ } }} END { print (count/length(string)*100"% difference") }' <<< ""66.6667%差异
以上百分比是错误的,因为以上两个词有很大的后面差异和预期的差异应该在5-10%之间。
你能告诉我在这种情况下该怎么做吗?
python,perl,shell任何东西都被接受。
发布于 2019-10-04 11:17:06
你似乎想比较一下字素簇。
一个字素聚类表示一个水平分割的文本单元,包括一些字素基(可以由一个韩语音节组成)以及应用于它的任意数量的非间距标记。
这只是一种“幻想”的方式,每一个字素聚类都是一个“视觉字符”。
我们确认一下。下面的程序可以让我们看到你的字符串,分为字素簇。
use open ':std', ':encoding(UTF-8)';
use charnames qw( :full );
for my $arg_idx (0..$#ARGV) {
my $arg = $ARGV[$arg_idx];
utf8::decode($arg);
for my $grapheme_cluster ($arg =~ /\X/g) {
printf("%s %v04X\n", $grapheme_cluster, $grapheme_cluster);
for my $code_point (unpack('W*', $grapheme_cluster)) {
printf(" %04X %s\n", $code_point, charnames::viacode($code_point));
}
}
print("\n") if $arg_idx != $#ARGV;
}对于你的一组字符串,我们得到
$ grapheme_clusters क़ौम $ grapheme_clusters क़ौम
कौ 0915.094C क़ौ 0915.093C.094C
0915 DEVANAGARI LETTER KA 0915 DEVANAGARI LETTER KA
093C DEVANAGARI SIGN NUKTA
094C DEVANAGARI VOWEL SIGN AU 094C DEVANAGARI VOWEL SIGN AU
म 092E म 092E
092E DEVANAGARI LETTER MA 092E DEVANAGARI LETTER MA 到目前为止还不错,这就产生了一个预期的差异。
对于另一组字符串,我们得到
$ grapheme_clusters अक्तूबर $ grapheme_clusters अक्टूबर
अ 0905 अ 0905
0905 DEVANAGARI LETTER A 0905 DEVANAGARI LETTER A
क् 0915.094D क् 0915.094D.200D
0915 DEVANAGARI LETTER KA 0915 DEVANAGARI LETTER KA
094D DEVANAGARI SIGN VIRAMA 094D DEVANAGARI SIGN VIRAMA
200D ZERO WIDTH JOINER
तू 0924.0942 टू 091F.0942
0924 DEVANAGARI LETTER TA 091F DEVANAGARI LETTER TTA
0942 DEVANAGARI VOWEL SIGN UU 0942 DEVANAGARI VOWEL SIGN UU
ब 092C ब 092C
092C DEVANAGARI LETTER BA 092C DEVANAGARI LETTER BA
र 0930 र 0930
0930 DEVANAGARI LETTER RA 0930 DEVANAGARI LETTER RA 啊,里面有个意想不到的ZERO WIDTH JOINER。如果我们要删除它(例如使用s/\N{ZERO WIDTH JOINER}//g,或者通过使用s/\pC//g删除所有控制字符),我们就会得到预期的单一差异。
既然我们已经确定了所需的内容,我们就可以编写解决方案了。
use List::Util qw( max );
sub count_diffs {
my ($s1, $s2) = @_;
s/\N{ZERO WIDTH JOINER}//g for $s1, $s2;
my @s1 = $s1 =~ /\X/g;
my @s2 = $s2 =~ /\X/g;
no warnings qw( uninitialized );
return 0+grep { $s1[$_] ne $s2[$_] } 0..max(0+@s1, 0+@s2)-1;
}这种方法的一个主要问题是它不能很好地处理插入或删除。例如,它认为abcdef和bcdef有6个不同之处。计算簇序的Levenshtein距离比按指数进行比较要有效得多。
use Algorithm::Diff qw( traverse_balanced );
sub count_diffs {
my ($s1, $s2) = @_;
s/\N{ZERO WIDTH JOINER}//g for $s1, $s2;
my @s1 = $s1 =~ /\X/g;
my @s2 = $s2 =~ /\X/g;
my $diffs = 0;
traverse_balanced(\@s1, \@s2,
{
DISCARD_A => sub { ++$diffs; },
DISCARD_B => sub { ++$diffs; },
CHANGE => sub { ++$diffs; },
},
);
return $diffs;
}最后,出于性能考虑,您不希望一次只比较两个字符串;您希望同时比较每个字符串和每个其他字符串。我不知道有什么很好用的解决方案。
发布于 2019-10-04 10:32:15
use utf8;
use List::Util qw(sum max);
use List::SomeUtils qw(pairwise);
sub norm { $_[0] =~ s/\pC//gr =~ /\X/g }
for my $pair (
[qw(अक्तूबर अक्टूबर)],
[qw(कौम क़ौम)],
) {
my @e0 = norm $pair->[0];
my @e1 = norm $pair->[1];
my $equal = sum pairwise { $a eq $b } @e0, @e1;
my $max = max scalar(@e0), scalar(@e1);
my $similarity = $equal / $max;
printf "%.1f%% similarity, %.1f%% difference\n",
100 * $similarity,
100 * (1 - $similarity);
}https://stackoverflow.com/questions/58233681
复制相似问题