Игра с персонажами деванагари

У меня есть что-то вроде

a = "बिक्रम मेरो नाम हो"

Я хочу достичь чего-то вроде

a[0] = बि a[1] = क्र a[3] = म

но так как म занимает 4 байта, а बि занимает 8 байтов, я не могу добраться до этой прямой. Итак, что можно сделать, чтобы достичь этого? В питоне.

5 ответов

Решение

Алгоритм расщепления текста на кластеры графем приведен в разделе 3.1 Приложения 29 к Unicode. Я не собираюсь реализовывать полный алгоритм для вас здесь, но я покажу вам примерно, как обращаться со случаем Деванагари, а затем вы можете прочитать Приложение для себя и посмотреть, что еще вам нужно реализовать.

unicodedata Модуль содержит информацию, необходимую для обнаружения кластеров графем.

>>> import unicodedata
>>> a = "बिक्रम मेरो नाम हो"
>>> [unicodedata.name(c) for c in a]
['DEVANAGARI LETTER BA', 'DEVANAGARI VOWEL SIGN I', 'DEVANAGARI LETTER KA', 
 'DEVANAGARI SIGN VIRAMA', 'DEVANAGARI LETTER RA', 'DEVANAGARI LETTER MA',
 'SPACE', 'DEVANAGARI LETTER MA', 'DEVANAGARI VOWEL SIGN E',
 'DEVANAGARI LETTER RA', 'DEVANAGARI VOWEL SIGN O', 'SPACE',
 'DEVANAGARI LETTER NA', 'DEVANAGARI VOWEL SIGN AA', 'DEVANAGARI LETTER MA',
 'SPACE', 'DEVANAGARI LETTER HA', 'DEVANAGARI VOWEL SIGN O']

В деванагари каждый кластер графем состоит из начальной буквы, необязательных пар virama (гласный убийца) и буквы и необязательного гласного знака. В регулярных выражениях это будет LETTER (VIRAMA LETTER)* VOWEL?, Вы можете определить, что есть что, посмотрев категорию Unicode для каждой кодовой точки:

>>> [unicodedata.category(c) for c in a]
['Lo', 'Mc', 'Lo', 'Mn', 'Lo', 'Lo', 'Zs', 'Lo', 'Mn', 'Lo', 'Mc', 'Zs',
 'Lo', 'Mc', 'Lo', 'Zs', 'Lo', 'Mc']

Письма категории Lo (Письмо, Другое), гласные знаки категории Mc (Марк, Spacing Combining), Вирама это категория Mn (Mark, Nonspacing) и пробелы являются категорией Zs (Разделитель, Пространство).

Итак, вот грубый подход к разделению кластеров графемы:

def splitclusters(s):
    """Generate the grapheme clusters for the string s. (Not the full
    Unicode text segmentation algorithm, but probably good enough for
    Devanagari.)

    """
    virama = u'\N{DEVANAGARI SIGN VIRAMA}'
    cluster = u''
    last = None
    for c in s:
        cat = unicodedata.category(c)[0]
        if cat == 'M' or cat == 'L' and last == virama:
            cluster += c
        else:
            if cluster:
                yield cluster
            cluster = c
        last = c
    if cluster:
        yield cluster

>>> list(splitclusters(a))
['बि', 'क्र', 'म', ' ', 'मे', 'रो', ' ', 'ना', 'म', ' ', 'हो']

Итак, вы хотите достичь чего-то вроде этого

a[0] = बि a[1] = क्र a[3] = म

Мой совет - отказаться от идеи, что индексирование строк соответствует символам, которые вы видите на экране. Деванагари, как и некоторые другие сценарии, плохо работают с программистами, которые выросли с латинскими символами. Я предлагаю прочитать стандартную главу 9 Unicode ( доступна здесь).

Похоже, вы пытаетесь разбить строку на кластеры графем. Индексирование строк само по себе не позволит вам сделать это. Хангул - еще один сценарий, плохо работающий с индексированием строк, хотя с сочетанием символов даже такие знакомые вещи, как испанский, вызовут проблемы.

Для этого вам понадобится внешняя библиотека, такая как ICU (если у вас нет много свободного времени). ICU имеет привязки Python.

>>> a = u"बिक्रम मेरो नाम हो"
>>> import icu
    # Note: This next line took a lot of guesswork.  The C, C++, and Java
    # interfaces have better documentation.
>>> b = icu.BreakIterator.createCharacterInstance(icu.Locale())
>>> b.setText(a)
>>> i = 0
>>> for j in b:
...     s = a[i:j]
...     print '|', s, len(s)
...     i = j
... 
| बि 2
| क् 2
| र 1
| म 1
|   1
| मे 2
| रो 2
|   1
| ना 2
| म 1
|   1
| हो 2

Обратите внимание, что некоторые из этих "символов" (кластеры графем) имеют длину 2, а некоторые имеют длину 1. Вот почему индексация строк проблематична: если я хочу получить кластер графем #69450 из текстового файла, то я должен линейно сканировать через весь файл и считать. Итак, ваши варианты:

  • Построить индекс (отчасти сумасшедший...)
  • Просто поймите, что вы не можете нарушить границы каждого персонажа. Объект итератора разрыва может идти как вперед, так и назад, поэтому, если вам нужно извлечь первые 140 символов строки, вы посмотрите на индекс 140 и вернетесь к предыдущему разрыву кластера графемы, таким образом, вы не закончите с забавным текстом. (Более того, вы можете использовать итератор разбиения слов для соответствующей локали.) Преимущество использования этого уровня абстракции (итераторы символов и т. П.) Заключается в том, что больше не имеет значения, какую кодировку вы используете: вы можете использовать UTF-8, UTF-16, UTF-32 и все это просто работает. Ну в основном работает.

Вы можете достичь этого с помощью простого регулярного выражения для любого движка, который поддерживает \X

демонстрация

К сожалению, Python не поддерживает совпадение графемы \X.

К счастью, предложенная замена, регулярное выражение, поддерживает \X:

>>> a = "बिक्रम मेरो नाम हो"
>>> regex.findall(r'\X', a)
['बि', 'क्', 'र', 'म', ' ', 'मे', 'रो', ' ', 'ना', 'म', ' ', 'हो']

Есть библиотека на чистом Python uniseg который предоставляет ряд утилит, включая итератор кластера графемы, который обеспечивает описанное вами поведение:

>>> a = u"बिक्रम मेरो नाम हो"
>>> from uniseg.graphemecluster import grapheme_clusters
>>> for i in grapheme_clusters(a): print(i)
... 
बि
क्
र
म

मे
रो

ना
म

हो

Он утверждает, что реализует полный алгоритм сегментации текста Unicode, описанный в http://www.unicode.org/reports/tr29/tr29-21.html.

Грамматика

Давайте очень быстро рассмотрим грамматику: Блок Деванагари. Вам, как разработчику, стоит обратить внимание на два класса персонажей:

  • Знак: это персонаж, который влияет на ранее встречавшегося персонажа. Например, этот персонаж:. Светлый кружок указывает расположение центра персонажа, на котором он должен быть размещен.
  • Буква / гласная / другое: это символ, на который могут влиять знаки. Например, этот персонаж:.

Результат комбинации а также : क्. Но комбинации могут расширяться, поэтомуक् а также षति действительно станет क्षति (в этом случае мы повернем первый символ вправо на 90 градусов, изменим некоторые стильные элементы и прикрепим его слева от второго символа).

Мой ответ здесь не в том, чтобы разрешить ситуацию с этими бесконечными (и невероятно красивыми) комбинациями, а просто с кластерами единичных букв и / или скоплениями единичных букв с их влияющими знаковыми символами. Если мы думаем: "Каковы символы этой строки Деванагари?", то это правильный путь, иначе любая комбинация букв будет формировать уникальный символ уникальной длины, и тогда большинство концепций и алгоритмов, связанных с системами букв, не сработают.

Так, например, символическое слово будет...

(letter) (letter) (sign) (sign) (letter) (sign)

В этом случае вам нужен результат...

[
    0=>(letter),
    1=>(letter) (sign) (sign),
    2=>(letter) (sign),
]

Код

Тогда логика не так уж и плоха, просто создайте цикл foreach, который идет в обратном порядке.

Я понимаю, что это код JavaScript ниже, но будут применяться те же принципы. Установитьsign-типы...

function getEndWordGroupings() {return {'2304':true,'2305':true,'2306':true,'2307':true,'2362':true,'2363':true,'2364':true,'2365':true,'2366':true,'2367':true,'2368':true,'2369':true,'2370':true,'2371':true,'2372':true,'2373':true,'2374':true,'2375':true,'2376':true,'2377':true,'2378':true,'2379':true,'2380':true,'2381':true,'2382':true,'2383':true,'2385':true,'2386':true,'2389':true,'2390':true,'2391':true,'2402':true,'2403':true,'2416':true,'2417':true,};}

И преобразовать строку в символы...

function stringToChars(args) {
    var word = args.word;
    var chars = [];
    
    var endings = getEndWordGroupings();
    
    var incluster = false;
    var cluster = '';
    
    var whitespace = new RegExp("\\s+");
    
    for(var i = word.length - 1; i >= 0; i--) {
        var character = word.charAt(i);
        var charactercode = word.charCodeAt(i);
        
        if(incluster) {
            if(whitespace.test(character)) {
                incluster = false;
                chars.push(cluster);
                cluster = '';
            } else if(endings[charactercode]) {
                chars.push(cluster);
                cluster = character;
            } else {
                incluster = false;
                cluster = character + cluster;
                chars.push(cluster);
                cluster = '';
            }
        } else if(endings[charactercode]) {
            incluster = true;
            cluster = character;
        } else if(whitespace.test(character)) {
            incluster = false;
            chars.push(cluster);
            cluster = '';
        } else {
            chars.push(character);
        }
    }
    
    if(cluster.length > 0) {
        chars.push(cluster);
    }
    
    return chars.reverse();
}

console.log(stringToChars({'word':'क्षऀति'}));</script>

Результаты, достижения

Выход:

["क्", "षऀ", "ति"]

Если бы я использовал простой синтаксический анализ, результат был бы

["क", "्", "ष", "त", "ि"]

Подсказка: видите два знака вверху со светлым кружком внутри? Этот светлый кружок указывает на местоположение персонажа, на которого действует знак. Оглядываясь назад на преобразованный перевод, очень легко увидеть, как буквы были объединены в новые символы. Аккуратно!

Индийские и нелатинские шрифты, такие как хангыль, обычно не следуют идее сопоставления строковых индексов с кодовыми точками. Как правило, работа с индийскими скриптами - это боль. Большинство символов составляют два байта, а некоторые редкие расширяются до трех. С Дравидианом, это не определенный порядок. Смотрите спецификацию Unicode для более подробной информации.

Тем не менее, проверьте здесь некоторые идеи о Unicode и Python с C++.

Наконец, как сказал Дитрих, вы можете проверить ICU тоже. Имеются привязки для C/C++ и java через icu4c и icu4j соответственно. В этом есть некоторая кривая обучения, поэтому я предлагаю вам выделить для этого много времени.:)

Другие вопросы по тегам