【问题标题】:Combining Devanagari characters组合梵文字符
【发布时间】:2011-07-24 06:26:21
【问题描述】:

我有类似的东西

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

我想实现类似的目标

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

但是由于 म 占用 4 个字节,而 बि 占用 8 个字节,我无法直截了当。 那么可以做些什么来实现这一目标呢?在 Python 中。

【问题讨论】:

  • 我从来没有真正玩过 devnagri,我现在肯定会尝试 :P,但是知道 devnagri 脚本,我觉得你提到的 'ma' 和 'be' 的区别可能因为,在devnagri中,“ma”是一个字符,但是“be”=“ba”+“e”(ba mai e ki maatra!:P就是我的意思)。如果表示的差异是因为这个,那么你应该能够通过做一些简单的位操作来检查和分离“matras”或像“kra”这样的一个半字母,然后在一个类似数据结构的列表中取出它们.如果找到解决方案,请发布解决方案。我很好奇!
  • #something like this may help # gist.github.com/950405

标签: python unicode indic devanagari


【解决方案1】:

Unicode Annex 29,第 3.1 节中给出了将文本拆分为字素簇的算法。在这里我不会为你实现完整的算法,但我会大致告诉你如何处理梵文的情况,然后你可以自己阅读附件,看看你还需要实现什么。

unicodedata module 包含检测字素簇所需的信息。

>>> 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 category 来判断哪个是哪个:

>>> [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(分隔符,空间)。

所以这里有一个粗略的分割字素簇的方法:

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

【讨论】:

  • 你好,加雷斯,你是天才!!!您的答案适用于各种 VIRAM 标志。 Like Nukta(字母下方的点)、Anuswar(字母上方的点)、Ardha-Chandra-Bindi(字母上方有月光的点)和 Kra(字符上的左斜线,如示例所示:क्र
【解决方案2】:

所以,你想实现这样的目标

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

我的建议是放弃字符串索引对应于您在屏幕上看到的字符的想法。天城文以及其他几个脚本不能与拉丁字符一起长大的程序员玩得很好。我建议阅读 Unicode 标准第 9 章 (available here)。

看起来您正在尝试将字符串分解为字形簇。字符串索引本身不会让您这样做。 Hangul 是另一个在字符串索引方面表现不佳的脚本,尽管在组合字符时,即使是像西班牙语这样熟悉的东西也会导致问题。

您将需要一个外部库,例如 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,那么我有线性扫描整个文件并计数。所以你的选择是:

  • 建立索引(有点疯狂...)
  • 请意识到您不能在每个字符边界上都打断。 break 迭代器对象能够向前和向后移动,因此如果您需要提取字符串的前 140 个字符,那么您可以查看索引 140 并将 backwards 迭代到上一个字形簇中断,这样你就不会得到有趣的文字。 (更好的是,您可以为适当的语言环境使用 分词 迭代器。)使用这种抽象级别(字符迭代器等)的好处是,您使用哪种编码不再重要:您可以使用 UTF-8、UTF-16、UTF-32,而且一切正常。嗯,大部分都有效。

【讨论】:

  • 对吗?您已将 क् (ka + virama) 和 र (ra) 输出为单独的集群,但根据 Unicode Text Segmentation algorithm,这些应该形成单个集群 क्र (kra)。
  • @Gareth:我怀疑这是一个“定制的字形簇”——这意味着它只会在某些语言环境中以这种方式分离。由于我提供了默认语言环境,因此不会进行“剪裁”。
  • @Gareth:在进一步的研究中,ICU 似乎不仅没有实施这些规则,而且它们也没有出现在 Unicode 语言环境数据库中。 Unicode 文本分割算法页面中定制的字素簇示例似乎不规范,因为我也找不到其他两个示例的规则。
【解决方案3】:

对于任何支持\X 的引擎,您都可以使用simple regex 来实现此目的

Demo

不幸的是,Python 的does not support 与 \X 字形匹配。

幸运的是,提议的替换 regex 确实支持 \X

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

【讨论】:

    【解决方案4】:

    像韩文这样的印度文和非拉丁文脚本通常不遵循将字符串索引与代码点匹配的想法。使用印度语脚本通常很痛苦。大多数字符是两个字节,一些罕见的字符扩展到三个字节。对于 Dravidian,它没有明确的顺序。有关详细信息,请参阅Unicode specification

    也就是说,请查看here 以了解有关使用 C++ 的 unicode 和 python 的一些想法。

    最后,正如Dietrich 所说,您可能还想查看ICU。它分别通过 icu4c 和 icu4j 为 C/C++ 和 java 提供绑定。这涉及到一些学习曲线,所以我建议您留出 一些 大量的时间。 :)

    【讨论】:

      【解决方案5】:

      语法

      让我们快速介绍一下语法:The Devanagari Block作为开发人员,您需要关注两个字符类:

      • 标志:这是一个影响先前出现的字符的字符。例如,这个字符:。浅色圆圈表示要放置的角色中心的位置。
      • 字母/元音/其他:这是一个可能受符号影响的字符。例如,这个字符:

      的组合结果:क्。但是组合可以扩展,所以क्षति实际上会变成क्षति(在这种情况下,我们将第一个字符向右旋转90度,修改一些时尚元素,并将其附加在左侧第二个字符)。

      我在这里的回答不是解决这些无限(并且非常漂亮)组合的情况,而是简单地解决单数字母的集群和/或单数字母的集群及其影响的符号字符。如果我们在想“这个梵文字符串的字符是什么?”,那么这是正确的方法,否则任何字母组合都会形成一个唯一长度的唯一字符,然后大多数与字母系统相关的概念和算法将失败。

      因此,例如,一个符号词将是......

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

      在这种情况下,你会想要结果...

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

      守则

      那么逻辑还不错,只需做一个反向循环的foreach循环即可。

      我知道这是下面的 JavaScript 代码,但同样的原则也适用。设置sign-types...

      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>
      

      结果

      输出:

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

      如果我使用普通解析,输出将是

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

      提示:看到上面两个带有浅色圆圈的标志了吗?那个浅圆圈表示符号影响的字符的位置。回顾转换后的翻译,很容易看出这些字母是如何组合成新字符的。整洁!

      【讨论】:

        【解决方案6】:

        有一个名为 uniseg 的纯 Python 库,它提供了许多实用程序,包括提供您描述的行为的字素集群迭代器:

        >>> 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中描述的完整的Unicode文本分割算法。

        【讨论】:

        • 输出不正确。 “क्र”应该是“क्र”。图书馆似乎有问题。
        • @shantanoo 这当然有可能——我会向作者报告:bitbucket.org/emptypage/uniseg-python
        猜你喜欢
        • 2020-02-02
        • 1970-01-01
        • 2018-02-11
        • 2013-10-21
        • 2011-01-05
        • 2021-04-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多