您似乎想要比较字素簇。
字素簇表示文本的水平可分割单元,由一些字素基(可能由韩语音节组成)以及应用到它的任意数量的非间距标记组成。
这只是每个字素簇都是“视觉字符”的一种“奇特”方式。
让我们确认一下。以下程序允许我们查看您的字符串,分为字形簇。
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 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;
}
最后,出于性能原因,您不想一次只比较两个字符串;您想一次将每个字符串与其他所有字符串进行比较。我不知道有什么现成的解决方案。