Подтвердить что ты не робот

Объединение персонажей деванагари

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

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

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

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

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

4b9b3361

Ответ 1

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

Модуль 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']

В Деванагари каждый кластер графем состоит из начальной буквы, необязательных пар вирамы (убийцы гласных) и буквы и необязательного знака гласного. В обозначениях регулярных выражений будет 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 (Letter, Other), знаки гласных - это категория Mc (Mark, Spacing Combining), virama - это категория Mn (Mark, Nospacing) и пробелы - это категория Zs (Separator, Space).

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

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))
['बि', 'क्र', 'म', ' ', 'मे', 'रो', ' ', 'ना', 'म', ' ', 'हो']

Ответ 2

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

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

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

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

Для достижения этой цели вам понадобится внешняя библиотека, такая как 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

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

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

Ответ 3

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

Демо

К сожалению, Python re не поддерживает соответствие \grapheme.

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

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

Ответ 4

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

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

मे
रो

ना
म

हो

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

Ответ 5

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

Тем не менее, проверьте здесь на некоторые идеи о unicode и python с С++.

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