Python保持Unicode一致的Java字符串偏移量

时间:2019-05-23 17:06:54

标签: java python string unicode

我们正在构建一个调用Java程序的Python 3程序。 Java程序(这是我们无法修改的第三方程序)用于标记字符串(查找单词)并提供其他注释。这些注释采用字符偏移的形式。

作为示例,我们可以为程序提供字符串数据,例如"lovely weather today"。它提供类似以下输出的内容:

0,6
7,14
15,20

其中0,6是与单词“ lovely”相对应的偏移量,7,14是与单词“ weather”相对应的偏移量,15,20是与单词“ today”相对应的偏移量源字符串。我们使用Python读取这些偏移量,以提取这些点处的文本并进行进一步处理。

一切都很好,只要字符在基本多语言平面(BMP)之内即可。但是,如果不是,则此Java程序报告的偏移量在Python端显示为所有错误。

例如,给定字符串"I feel today",Java程序将输出:

0,1
2,6
7,9
10,15

在Python方面,这些翻译为:

0,1    "I"
2,6    "feel"
7,9    " "
10,15  "oday"

最后一个索引在技术上无效。 Java将“”视为长度2,从Python程序的角度来看,这导致该点之后的所有注释都偏离一个。

大概是因为Java在内部以UTF-16esqe方式对字符串进行编码,并且所有字符串操作都对那些UTF-16esque code units起作用。另一方面,Python字符串似乎可以对实际的unicode字符(代码点)进行操作。因此,当字符显示在BMP之外时,Java程序将其视为长度2,而Python将其视为长度1。

所以现在的问题是:在Python使用偏移之前“校正”这些偏移的最佳方法是什么,以使注释子字符串与Java程序打算输出的内容一致?

2 个答案:

答案 0 :(得分:2)

您可以将字符串转换为UTF16编码的字节数组,然后使用偏移量(乘以2,因为每个UTF-16代码单元有两个字节)来索引该数组:

x = "I feel  today"
y = bytearray(x, "UTF-16LE")

offsets = [(0,1),(2,6),(7,9),(10,15)]

for word in offsets:
  print(str(y[word[0]*2:word[1]*2], 'UTF-16LE'))

输出:

I
feel

today

或者,您可以将字符串中的每个python字符分别转换为UTF-16并计算所需的代码单元数。这使您可以将代码单元(来自Java)的索引映射到Python字符的索引:

from itertools import accumulate

x = "I feel  today"
utf16offsets = [(0,1),(2,6),(7,9),(10,15)] # from java program

# map python string indices to an index in terms of utf-16 code units
chrLengths = [len(bytearray(ch, "UTF-16LE"))//2 for ch in x]
utf16indices = [0] + list(itertools.accumulate(chrLengths))
# reverse the map so that it maps utf16 indices to python indices
index_map = dict((x,i) for i, x in enumerate(utf16indices))

# convert the offsets from utf16 code-unit indices to python string indices
offsets = [(index_map[o[0]], index_map[o[1]]) for o in utf16offsets]

# now you can just use those indices as normal
for word in offsets:
  print(x[word[0]:word[1]])

输出:

I
feel

today

上面的代码很杂乱,也许可以使它更清晰,但是您明白了。

答案 1 :(得分:1)

这解决了给定正确编码的问题,在我们的情况下,该编码看起来是'UTF-16BE'

def correct_offsets(input, offsets, encoding):
  offset_list = [{'old': o, 'new': [o[0],o[1]]} for o in offsets]

  for idx in range(0, len(input)):
    if len(input[idx].encode(encoding)) > 2:
      for o in offset_list:
        if o['old'][0] > idx:
          o['new'][0] -= 1
        if o['old'][1] > idx:
          o['new'][1] -= 1

  return [o['new'] for o in offset_list]

这可能效率很低。我非常欢迎任何性能方面的改进。