获取拉丁字符的所有Unicode变体

6
例如,对于字符"a",我想要得到一个字符串(字符列表),如"aàáâãäåāăą"(不确定这个示例列表是否完整...)(基本上是所有具有名称"Latin Small Letter A with *"的unicode字符)。
是否有一种通用的方法来实现这个功能?
我是在询问Python方面的问题,但如果答案更通用,那也可以。无论如何,我会感激提供Python代码片段。Python版本>=3.5即可。但我认为您需要访问Unicode数据库,例如Python模块unicodedata,我更喜欢它而不是其他外部数据源。
我可以想象出这样的解决方案:
def get_variations(char):
   import unicodedata
   name = unicodedata.name(char)
   chars = char
   for variation in ["WITH CEDILLA", "WITH MACRON", ...]:
      try: 
          chars += unicodedata.lookup("%s %s" % (name, variation))
      except KeyError:
          pass
   return chars

如果你只是指重音字符,尝试迭代所有拉丁组合重音。 - tripleee
在某个层面上,您必须考虑非间距组合标记(类别Mn)。如果您想列出所有带有拉丁字母和任何组合标记的字符串,则应该知道允许任意数量的组合标记。在这种情况下,答案是一个无限集合。但是,也许您只想要可以规范化的字符,这意味着对于许多这样的“字符”,存在两个不同的字符串具有相同的语义值。 - Tom Blodget
根据您的需求,如果考虑非拉丁文脚本,事情可能会变得更加复杂。西里尔字母A通常与拉丁字母A或希腊大写字母α无法区分。然后,您还有看起来像K的开尔文符号。请参阅http://www.guido-flohr.net/unicode-regex-pitfalls/。 - Guido Flohr
4个回答

6

首先,获取一组Unicode组合变音字符;它们都是连续的,所以这很容易,例如:

# Unicode combining diacritical marks run from 768 to 879, inclusive
combining_chars = ''.join(map(chr, range(768, 880)))

现在定义一个函数,尝试将每个字符与基本ASCII字符组合。当组合后的正规形式长度为1时(表示ASCII + 组合成为单个Unicode序数),则保存该字符。
import unicodedata

def get_unicode_variations(letter):
    if len(letter) != 1:
        raise ValueError("letter must be a single character to check for variations")
    variations = []
    # We could just loop over map(chr, range(768, 880)) without caching
    # in combining_chars, but that increases runtime ~20%
    for combiner in combining_chars:
        normalized = unicodedata.normalize('NFKC', letter + combiner)
        if len(normalized) == 1:
            variations.append(normalized)
    return ''.join(variations)

这种方法的优点是不需要在unicodedata数据库中手动查找字符串,也不需要硬编码所有可能的组合字符描述。任何组成单个字符的内容都会被包含;在我的机器上,检查运行时间少于50微秒,因此如果您不经常这样做,则成本是合理的(如果您打算重复使用相同的参数并希望避免每次重新计算,可以使用functools.lru_cache进行装饰)。
如果您想从其中一个字符中获得所有构建内容的话,可以进行更详尽的搜索,但这将需要更长的时间(除非每个参数只调用一次,否则functools.lru_cache几乎是必需的):
import functools
import sys
import unicodedata

@functools.lru_cache(maxsize=None)
def get_unicode_variations_exhaustive(letter): 
    if len(letter) != 1:
        raise ValueError("letter must be a single character to check for variations")
    variations = [] 
    for testlet in map(chr, range(sys.maxunicode)): 
        if letter in unicodedata.normalize('NFKD', testlet) and testlet != letter: 
            variations.append(testlet) 
    return ''.join(variations) 

这段话是关于搜索包含特定字母的字符的方法。它会寻找任何分解为包含目标字母的形式的字符;这意味着第一次搜索大约需要三分之一秒钟,并且结果包括不仅仅是字符的修改版本(例如'L'的结果将包括℡,它并不是真正的“修改”的'L'),但这是尽可能全面的方法。

我觉得NFKD可能更有用,但问题有点模糊。 - Josh Lee
@JoshLee:NFKD?我可以看出NFKC很有用(如果你想接受具有ASCII兼容等效物的非ASCII输入),但是NFKD不会组合字符,因此对于查找组合结果来说并不是特别有用。 - ShadowRanger
@JoshLee:我已经添加了另一种解决方案;原帖作者可以选择哪种适合他们。 - ShadowRanger
@Albert: 我在两个实现中有意忽略了字符本身;如果您喜欢,可以随时显式地添加回来(在第二种情况下,删除测试以防止自我插入)。 如果由于多个组合字符产生相同的字符而导致重复项,则可以将最后一行更改为:return ''.join(dict.fromkeys(variations)),这样在 Python 3.6+ 上将去重同时保留首次出现的顺序(在 3.5 及更早版本中,您将使用 collections.OrderedDict.fromkeys)。 - ShadowRanger
@Albert:关于 q,我使用的是11.0.0版本,仍然没有输出,而详尽版返回了 '⒬ⓠ㏃q'。我怀疑带有变音符号的 q 没有单一的编码形式,它只能由 q 和一个组合变音符号组成。 - ShadowRanger
显示剩余6条评论

2
您可以直接使用Unicode数据库的分解映射。以下代码检查具有以某个字母开头的分解的字符的所有映射:
def get_unicode_variations(letter):
    letter_code = ord(letter)
    # For some characters, you might want to check all
    # code points up to 0x10FFFF
    for i in range(65536):
        decomp = unicodedata.decomposition(chr(i))
        # Mappings starting with '<...>' indicate a
        # compatibility mapping (NFKD, NFKC) which we ignore.
        while decomp != '' and not decomp.startswith('<'):
            first_code = int(decomp.split()[0], 16)
            if first_code == letter_code:
                print(chr(i), unicodedata.name(chr(i)))
                break
            # Try to decompose further
            decomp = unicodedata.decomposition(chr(first_code))

这样做如果要处理多个字符就不太高效了。对于字母a上面的代码会打印出
à LATIN SMALL LETTER A WITH GRAVE
á LATIN SMALL LETTER A WITH ACUTE
â LATIN SMALL LETTER A WITH CIRCUMFLEX
ã LATIN SMALL LETTER A WITH TILDE
ä LATIN SMALL LETTER A WITH DIAERESIS
å LATIN SMALL LETTER A WITH RING ABOVE
ā LATIN SMALL LETTER A WITH MACRON
ă LATIN SMALL LETTER A WITH BREVE
ą LATIN SMALL LETTER A WITH OGONEK
ǎ LATIN SMALL LETTER A WITH CARON
ǟ LATIN SMALL LETTER A WITH DIAERESIS AND MACRON
ǡ LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON
ǻ LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE
ȁ LATIN SMALL LETTER A WITH DOUBLE GRAVE
ȃ LATIN SMALL LETTER A WITH INVERTED BREVE
ȧ LATIN SMALL LETTER A WITH DOT ABOVE
ḁ LATIN SMALL LETTER A WITH RING BELOW
ạ LATIN SMALL LETTER A WITH DOT BELOW
ả LATIN SMALL LETTER A WITH HOOK ABOVE
ấ LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE
ầ LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE
ẩ LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE
ẫ LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE
ậ LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW
ắ LATIN SMALL LETTER A WITH BREVE AND ACUTE
ằ LATIN SMALL LETTER A WITH BREVE AND GRAVE
ẳ LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE
ẵ LATIN SMALL LETTER A WITH BREVE AND TILDE
ặ LATIN SMALL LETTER A WITH BREVE AND DOT BELOW

1

据我所知,目前没有这样的工具,但是你可以自己构建一个。只需查找特殊字符的起始和结束编号即可。你可以使用unicode character table来查找编号。然后,为每个字符创建一个使用这些编号的列表。

ranges = {
  'A': (192, 199),
  'B': (0, 0),
  'E': (200, 204),
  ...
}

map = {}
for char, rng in ranges.items():
  start, end = rng 
  map[char] = char + ''.join([chr(i) for i in range(start, end)])

这将生成一个地图,如下所示:

{
  'A': 'AÀÁÂÃÄÅÆ'
  'B': 'B',
  'E': 'EÈÉÊË',
  ...
}

0

使用unichars

› unichars -a | grep -i 'Latin Small Letter A with'
 à  U+000E0 LATIN SMALL LETTER A WITH GRAVE
 á  U+000E1 LATIN SMALL LETTER A WITH ACUTE
 â  U+000E2 LATIN SMALL LETTER A WITH CIRCUMFLEX
 ã  U+000E3 LATIN SMALL LETTER A WITH TILDE
 ä  U+000E4 LATIN SMALL LETTER A WITH DIAERESIS
 å  U+000E5 LATIN SMALL LETTER A WITH RING ABOVE
 ā  U+00101 LATIN SMALL LETTER A WITH MACRON
 ă  U+00103 LATIN SMALL LETTER A WITH BREVE
 ą  U+00105 LATIN SMALL LETTER A WITH OGONEK
 ǎ  U+001CE LATIN SMALL LETTER A WITH CARON
 ǟ  U+001DF LATIN SMALL LETTER A WITH DIAERESIS AND MACRON
 ǡ  U+001E1 LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON
 ǻ  U+001FB LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE
 ȁ  U+00201 LATIN SMALL LETTER A WITH DOUBLE GRAVE
 ȃ  U+00203 LATIN SMALL LETTER A WITH INVERTED BREVE
 ȧ  U+00227 LATIN SMALL LETTER A WITH DOT ABOVE
 ᶏ  U+01D8F LATIN SMALL LETTER A WITH RETROFLEX HOOK
 ◌ᷲ  U+01DF2 COMBINING LATIN SMALL LETTER A WITH DIAERESIS
 ḁ  U+01E01 LATIN SMALL LETTER A WITH RING BELOW
 ẚ  U+01E9A LATIN SMALL LETTER A WITH RIGHT HALF RING
 ạ  U+01EA1 LATIN SMALL LETTER A WITH DOT BELOW
 ả  U+01EA3 LATIN SMALL LETTER A WITH HOOK ABOVE
 ấ  U+01EA5 LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE
 ầ  U+01EA7 LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE
 ẩ  U+01EA9 LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE
 ẫ  U+01EAB LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE
 ậ  U+01EAD LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW
 ắ  U+01EAF LATIN SMALL LETTER A WITH BREVE AND ACUTE
 ằ  U+01EB1 LATIN SMALL LETTER A WITH BREVE AND GRAVE
 ẳ  U+01EB3 LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE
 ẵ  U+01EB5 LATIN SMALL LETTER A WITH BREVE AND TILDE
 ặ  U+01EB7 LATIN SMALL LETTER A WITH BREVE AND DOT BELOW
 ⱥ  U+02C65 LATIN SMALL LETTER A WITH STROKE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接