【问题标题】:Devnagari String comparison梵文字符串比较
【发布时间】:2020-02-02 15:09:41
【问题描述】:

我在文本文件中有几十万个 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 什么都可以接受。

【问题讨论】:

标签: python shell perl


【解决方案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);
}

【讨论】:

  • “कंडिशंस कंडीशन”失败,结果相似度为 25.0%,差异为 75.0%
  • @Amol, The contents of the string in detail. 你期待什么?为什么?
  • 失败了怎么办? कं = कं, डि ≠ डी, शं ≠ श, स ≠ न 四分之一是 25%
  • 我的目的是写错字。
  • @Amol,这仍然不能解释为什么你说“失败了”,它有3 differences。 (您需要包含@ikegami 以便我收到通知,因为我没有写您要回复的帖子。)
【解决方案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;
}

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-02-11
    • 1970-01-01
    • 2011-07-06
    • 1970-01-01
    相关资源
    最近更新 更多