Объединение персонажей деванагари - PullRequest
25 голосов
/ 24 июля 2011

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

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

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

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

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

Ответы [ 5 ]

20 голосов
/ 24 июля 2011

Алгоритм разбиения текста на графемные кластеры приведен в Unicode Annex 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']

В Деванагари каждый кластер графем состоит из начальной буквы, необязательных пар 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 (Марк, объединение пробелов), virama - к категории Mn (Марка, без пробелов), а пробелы относятся к категории Zs (Разделитель, 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))
['बि', 'क्र', 'म', ' ', 'मे', 'रो', ' ', 'ना', 'म', ' ', 'हो']
14 голосов
/ 24 июля 2011

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

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 и итерируете назад к предыдущему разрыву кластера графемы, что как вы не получите смешной текст. (Еще лучше, вы можете использовать итератор word break для соответствующей локали.) Преимущество использования этого уровня абстракции (итераторов символов и т. П.) Заключается в том, что больше не имеет значения, какую кодировку вы используете: вы можно использовать UTF-8, UTF-16, UTF-32, и все это просто работает. Ну, в основном работает.
2 голосов
/ 07 мая 2015

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

Демо

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

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

>>> a = "बिक्रम मेरो नाम हो"
>>> regex.findall(r'\X', a)
['बि', 'क्', 'र', 'म', ' ', 'मे', 'रो', ' ', 'ना', 'म', ' ', 'हो']
1 голос
/ 13 июля 2016

Существует библиотека на чистом 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.

0 голосов
/ 24 июля 2011

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

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

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

...