Похоже, вы хотите сравнить кластеры графем .
Кластер графем представляет горизонтально сегментируемую единицу текста, состоящую из некоторой базы графем (которая может состоять из корейского языка)слог) вместе с любым количеством непространственных меток, примененных к нему.
Это просто «причудливый» способ, которым каждый кластер графем является «визуальным символом».
Давайте подтвердим. Следующая программа позволяет нам взглянуть на вашу строку, разделенную на кластеры графем.
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 отличий. Было бы гораздо эффективнее вычислять расстояние Левенштейна последовательности кластеров, а не сравнивать по индексу.
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;
}
Наконец, из соображений производительности не нужно сравниватьтолько две строки одновременно;Вы хотите сравнить каждую строку с каждой другой строкой сразу. Я не знаю легкодоступного решения для этого.