Devnagari字符串比较

问题描述 投票:2回答:2

我在文本文件中有数十万个devnagari单词(每行一个单词)。我必须在另一个文件中复制最相似的单词,例如“अक्غूबर,अक्‍अर”,“कौम,़ौम”,以进行更正。最多允许两个地方的差异。为此,我使用“ awk”来查找单词的差异并将相似的单词复制到另一个文件中。但是它失败了,因为此命令仅适用于罗马字符,不适用于德文加里字符。

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,接受任何外壳程序。

python shell perl
2个回答
2
投票

您似乎想比较字素簇

一个字素簇表示文本的水平可分割单元,由一些字素基(可能由一个韩国音节组成)以及应用于其上的任意数量的非间距标记组成。

这只是每个字素簇是一个“视觉角色”的“幻想”方式。

让我们确认。以下程序使我们可以查看您的字符串,并将其分为字素簇。

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;
}

这种方法的主要问题是,它不能很好地处理插入或删除操作。例如,它认为abcdefbcdef具有6个差异。计算聚类序列的Levenshtein distance而不是按索引进行比较会更加有效。

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;
}

最后,出于性能原因,您不想一次只比较两个字符串;您想一次将每个字符串与其他所有字符串进行比较。我不知道有什么可行的解决方案。


1
投票
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);
}
© www.soinside.com 2019 - 2024. All rights reserved.