草庐IT

python - 用变音符号编码阿拉伯字母(如果存在)

coder 2023-08-16 原文

我正在做一个使用 RNN 的深度学习项目。我想在将数据馈送到网络之前对其进行编码。输入是阿拉伯语诗句,其中的变音符号在 Python 中被视为单独的字符。我应该用数字 编码/表示字符后面的字符如果后面的字符是变音符号,否则我只对字符 进行编码.

为数以百万计的诗句这样做,是希望使用 lambdamap .但是,我不能一次迭代两个字符,即希望:

map(lambda ch, next_ch: encode(ch + next_ch) if is_diacritic(next_ch) else encode(ch), verse)

我在这个问题背后的意图是找到完成上述任务的最快方法。对 lambda 函数没有限制,但 for循环答案不是我要找的。

非阿拉伯人的一个接近的示例,假设您要对以下文本进行编码:
 XXA)L_I!I%M<LLL>MMQ*Q

您想在将字母与后面的字母连接后对字母进行编码 如果是特殊字符 , 否则只对字母进行编码。

输出:
['X', 'X', 'A)', 'L_', 'I!', 'I%', 'M<', 'L', 'L', 'L>', 'M', 'M', 'Q*', 'Q']

对于阿拉伯人:

诗句示例:

“قفا نبك من ذِكرى حبيب ومنزل بسِقطِ اللّوى بينَ الدَّخول فحَوْمل”

变音符号是字母上方的这些小符号(即 ّ , ْ )

[更新]

Range of diacritics开始于 64B HEX or 1611 INT并在 652 HEX or 1618 INT 结束.

和字母 621 HEX - 1569 INT63A HEX - 1594 INT来自 641 HEX - 1601 INT64A HEX - 1610 INT

一个字母最多可以有一个变音符号。

额外的信息:

与我正在做的类似的编码方法是将经文的二进制形式表示为形状为 (number of bits needed, number of characters in a verse) 的矩阵。 .计算位数和字符数如果存在,我们将每个字母与其变音符号组合在一起后 .

例如,假设这节经文如下,并且变音符号是特殊字符:
X+Y_XX+YYYY_

不同的字母组合是:
['X', 'X+', 'X_', 'Y', 'Y+', 'Y_']  

因此我需要 3位(至少)来表示这些 6字符,所以 number of bits needed3
考虑以下编码:
{
'X' : 000,
'X+': 001,
'X_': 010,
'Y':  011,
'Y+': 100,
'Y_': 101,
}

我可以将矩阵中的示例表示为(二进制表示是垂直的):
X+     Y_    X    X+    Y    Y    Y    Y_
0      1     0    0     0    0    0    1
0      0     0    0     1    1    1    0
1      1     0    1     1    1    1    1

这就是为什么我希望首先将变音符号与字母结合起来。

注:Iterate over a string 2 (or n) characters at a time in PythonIterating each character in a string using Python不要给出预期的答案。

最佳答案

我要在这里用 numpy 把我的帽子扔进戒指。您可以使用以下命令将字符串转换为可用格式

arr = np.array([verse]).view(np.uint32)

您可以屏蔽以下字符变音的位置:
mask = np.empty(arr.shape, dtype=np.bool)
np.bitwise_and((arr[1:] > lower), (arr[1:] < upper), out=mask[:-1])
mask[-1] = False

这里,范围 [upper, lower]是一种检查变音符号的方法。随心所欲地实现实际检查。在这个例子中,我使用了完整的形式 bitwise_and empty 以避免最后一个元素的潜在昂贵的追加。

现在,如果您有一种将代码点编码为数字的数值方法,我相信您可以对其进行矢量化,您可以执行以下操作:
combined = combine(letters=arr[mask], diacritics=arr[1:][mask[:-1]])

要获得剩余的未组合字符,您必须同时删除变音符号和它们绑定(bind)的字符。我能想到的最简单的方法是将面具涂抹在右侧并使其无效。同样,我假设您也有一个矢量化方法来对单个字符进行编码:
smeared = mask.copy()
smeared[1:] |= mask[:-1]
single = encode(arr[~smeared])

将结果组合成最终数组在概念上很简单,但需要几个步骤。结果将是 np.count_nonzeros(mask) 比输入短的元素,因为正在删除变音符号。我们需要按索引量移动所有掩码元素。这是一种方法:
ind = np.flatnonzero(mask)
nnz = ind.size
ind -= np.arange(nnz)

output = np.empty(arr.size - nnz, dtype='U1')
output[ind] = combined

# mask of unmodified elements
out_mask = np.ones(output.size, dtype=np.bool)
out_mask[ind] = False
output[out_mask] = single

我建议使用 numpy 的原因是它应该能够以这种方式在几秒钟内处理几百万个字符。将输出作为字符串返回应该很简单。

建议实现

我一直在思考你的问题,并决定尝试一些时间和可能的实现。我的想法是映射 中的 unicode 字符0x0621-0x063A , 0x0641-0x064A (26 + 10 = 36 个字母)到 uint16 的低 6 位, 和字符 0x064B-0x0652 (8 个变音符号)到下一个更高的 3 位,假设这些实际上是您需要的唯一变音符号:
def encode_py(char):
    char = ord(char) - 0x0621
    if char >= 0x20:
        char -= 5
    return char

def combine_py(char, diacritic):
    return encode_py(char) | ((ord(diacritic) - 0x064A) << 6)

用 NumPy 的话来说:
def encode_numpy(chars):
    chars = chars - 0x0621
    return np.subtract(chars, 5, where=chars > 0x20, out=chars)

def combine_numpy(chars, diacritics):
    chars = encode_numpy(chars)
    chars |= (diacritics - 0x064A) << 6
    return chars

您可以选择进一步编码以稍微缩短表示,但我不建议这样做。这种表示的优点是与经文无关,因此您可以比较不同经文的部分,而且不必担心根据一起编码的经文的数量会得到哪种表示。您甚至可以屏蔽所有代码的最高位以比较原始字符,而无需使用变音符号。

因此,假设您的诗句是这些范围内随机生成的数字的集合,随机生成的变音符号最多跟随一个字母。出于比较目的,我们可以很容易地生成一串长度约为百万的字符串:
import random

random.seed(0xB00B5)

alphabet = list(range(0x0621, 0x063B)) + list(range(0x0641, 0x064B))
diactitics = list(range(0x064B, 0x0653))

alphabet = [chr(x) for x in alphabet]
diactitics = [chr(x) for x in diactitics]

def sample(n=1000000, d=0.25):
    while n:
        yield random.choice(alphabet)
        n -= 1
        if n and random.random() < d:
            yield random.choice(diactitics)
            n -= 1

data = ''.join(sample())

该数据具有完全随机分布的字符,任何字符后跟变音符号的几率约为 25%。在我不太强大的笔记本电脑上生成只需几秒钟。

numpy 转换如下所示:
def convert_numpy(verse):
    arr = np.array([verse]).view(np.uint32)
    mask = np.empty(arr.shape, dtype=np.bool)
    mask[:-1] = (arr[1:] >= 0x064B)
    mask[-1] = False

    combined = combine_numpy(chars=arr[mask], diacritics=arr[1:][mask[:-1]])

    smeared = mask.copy()
    smeared[1:] |= mask[:-1]
    single = encode_numpy(arr[~smeared])

    ind = np.flatnonzero(mask)
    nnz = ind.size
    ind -= np.arange(nnz)

    output = np.empty(arr.size - nnz, dtype=np.uint16)
    output[ind] = combined

    # mask of unmodified elements
    out_mask = np.ones(output.size, dtype=np.bool)
    out_mask[ind] = False
    output[out_mask] = single

    return output

基准

现在让我们 %timeit看看情况如何。首先,这里是其他实现。我将所有内容转换为 numpy 数组或整数列表以进行公平比较。我还做了一些小的修改,使函数返回相同数量的列表以验证准确性:
from itertools import tee, zip_longest
from functools import reduce

def is_diacritic(c):
    return ord(c) >= 0x064B

def pairwise(iterable, fillvalue):
    """ Slightly modified itertools pairwise recipe
    s -> (s0,s1), (s1,s2), (s2, s3), ... 
    """
    a, b = tee(iterable)
    next(b, None)
    return zip_longest(a, b, fillvalue=fillvalue)

def combine_py2(char, diacritic):
    return char | ((ord(diacritic) - 0x064A) << 6)

def convert_FHTMitchell(verse):
    def convert(verse):
        was_diacritic = False  # variable to keep track of diacritics -- stops us checking same character twice

        # fillvalue will not be encoded but ensures last char is read
        for this_char, next_char in pairwise(verse, fillvalue='-'):
            if was_diacritic:  # last next_char (so this_char) is diacritic
                was_diacritic = False
            elif is_diacritic(next_char):
                yield combine_py(this_char, next_char)
                was_diacritic = True
            else:
                yield encode_py(this_char)

    return list(convert(verse))

def convert_tobias_k_1(verse):
    return reduce(lambda lst, x: lst + [encode_py(x)] if not is_diacritic(x) else lst[:-1] + [combine_py2(lst[-1], x)], verse, [])

def convert_tobias_k_2(verse):
    res = []
    for x in verse:
        if not is_diacritic(x):
            res.append(encode_py(x))
        else:
            res[-1] = combine_py2(res[-1], x)
    return res

def convert_tobias_k_3(verse):
    return [combine_py(x, y) if y and is_diacritic(y) else encode_py(x) for x, y in zip_longest(verse, verse[1:], fillvalue="") if not is_diacritic(x)]

现在是时间:
%timeit result_FHTMitchell = convert_FHTMitchell(data)
338 ms ± 5.09 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit result_tobias_k_1 = convert_tobias_k_1(data)
Aborted, took > 5min to run. Appears to scale quadratically with input size: not OK!

%timeit result_tobias_k_2 = convert_tobias_k_2(data)
357 ms ± 4.94 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit result_tobias_k_3 = convert_tobias_k_3(data)
466 ms ± 4.62 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit result_numpy = convert_numpy(data)
30.2 µs ± 162 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

结果数组/列表的比较表明它们也相等:
np.array_equal(result_FHTMitchell, result_tobias_k_2)  # True
np.array_equal(result_tobias_k_2, result_tobias_k_3)   # True
np.array_equal(result_tobias_k_3, result_numpy)        # True

我正在使用 array_equal这是因为它执行所有必要的类型转换来验证实际数据。

所以这个故事的寓意是有很多方法可以做到这一点,在您进入交叉引用和其他真正耗时的任务之前,解析数百万个字符本身不应该过于昂贵。主要的一点是不要使用 reduce在列表上,因为您将重新分配 很多比你需要的更多。即使是一个简单的for循环可以很好地满足您的目的。尽管 numpy 比其他实现快十倍左右,但它并没有带来巨大的优势。

解码

为了完整起见,这里有一个函数来解码你的结果:
def decode(arr):
    mask = (arr > 0x3F)
    nnz = np.count_nonzero(mask)
    ind = np.flatnonzero(mask) + np.arange(nnz)

    diacritics = (arr[mask] >> 6) + 41
    characters = (arr & 0x3F)
    characters[characters >= 27] += 5

    output = np.empty(arr.size + nnz, dtype='U1').view(np.uint32)
    output[ind] = characters[mask]
    output[ind + 1] = diacritics

    output_mask = np.zeros(output.size, dtype=np.bool)
    output_mask[ind] = output_mask[ind + 1] = True
    output[~output_mask] = characters[~mask]

    output += 0x0621

    return output.base.view(f'U{output.size}').item()

作为旁注,我在这里所做的工作激发了这个问题:Converting numpy arrays of code points to and from strings

关于python - 用变音符号编码阿拉伯字母(如果存在),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54323714/

有关python - 用变音符号编码阿拉伯字母(如果存在)的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby-on-rails - 如果为空或不验证数值,则使属性默认为 0 - 2

    我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val

  3. ruby-on-rails - 使用一系列等级计算字母等级 - 2

    这里是Ruby新手。完成一些练习后碰壁了。练习:计算一系列成绩的字母等级创建一个方法get_grade来接受测试分数数组。数组中的每个分数应介于0和100之间,其中100是最大分数。计算平均分并将字母等级作为字符串返回,即“A”、“B”、“C”、“D”、“E”或“F”。我一直返回错误:avg.rb:1:syntaxerror,unexpectedtLBRACK,expecting')'defget_grade([100,90,80])^avg.rb:1:syntaxerror,unexpected')',expecting$end这是我目前所拥有的。我想坚持使用下面的方法或.join,

  4. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  5. ruby - 如果指定键的值在数组中相同,如何合并哈希 - 2

    我有一个这样的哈希数组:[{:foo=>2,:date=>Sat,01Sep2014},{:foo2=>2,:date=>Sat,02Sep2014},{:foo3=>3,:date=>Sat,01Sep2014},{:foo4=>4,:date=>Sat,03Sep2014},{:foo5=>5,:date=>Sat,02Sep2014}]如果:date相同,我想合并哈希值。我对上面数组的期望是:[{:foo=>2,:foo3=>3,:date=>Sat,01Sep2014},{:foo2=>2,:foo5=>5:date=>Sat,02Sep2014},{:foo4=>4,:dat

  6. ruby - 匹配大写字母并用后续字母填充,直到一定的字符串长度 - 2

    我有一个驼峰式字符串,例如:JustAString。我想按照以下规则形成长度为4的字符串:抓取所有大写字母;如果超过4个大写字母,只保留前4个;如果少于4个大写字母,则将最后大写字母后的字母大写并添加字母,直到长度变为4。以下是可能发生的3种情况:ThisIsMyString将产生TIMS(大写字母);ThisIsOneVeryLongString将产生TIOV(前4个大写字母);MyString将生成MSTR(大写字母+tr大写)。我设法用这个片段解决了前两种情况:str.scan(/[A-Z]/).first(4).join但是,我不太确定如何最好地修改上面的代码片段以处理最后一种

  7. ruby-on-rails - 如果我将 ruby​​ 版本 2.5.1 与 rails 版本 2.3.18 一起使用会怎样? - 2

    如果我使用ruby​​版本2.5.1和Rails版本2.3.18会怎样?我有基于rails2.3.18和ruby​​1.9.2p320构建的rails应用程序,我只想升级ruby的版本,而不是rails,这可能吗?我必须面对哪些挑战? 最佳答案 GitHub维护apublicfork它有针对旧Rails版本的分支,有各种变化,它们一直在运行。有一段时间,他们在较新的Ruby版本上运行较旧的Rails版本,而不是最初支持的版本,因此您可能会发现一些关于需要向后移植的有用提示。不过,他们现在已经有几年没有使用2.3了,所以充其量只能让更

  8. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

  9. ruby-on-rails - rspec - 如何检查方法是否存在? - 2

    我的模型有defself.empty_building//stuffend我怎样才能对这个现有的进行rspec?,已经尝试过:describe"empty_building"dosubject{Building.new}it{shouldrespond_to:empty_building}endbutgetting:Failure/Error:it{shouldrespond_to:empty_building}expected#torespondto:empty_building 最佳答案 你有一个类方法self.empty_bu

  10. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

随机推荐