【问题标题】:Converting numpy arrays of code points to and from strings将代码点的numpy数组转换为字符串和从字符串转换
【发布时间】:2019-01-29 15:31:04
【问题描述】:

我有一个很长的 unicode 字符串:

alphabet = range(0x0FFF)
mystr = ''.join(chr(random.choice(alphabet)) for _ in range(100))
mystr = re.sub('\W', '', mystr)

我想将其视为一系列代码点,所以目前,我正在执行以下操作:

arr = np.array(list(mystr), dtype='U1')

我希望能够将字符串作为数字进行操作,并最终获得一些不同的代码点。现在我想反转转换:

mystr = ''.join(arr.tolist())

这些转换相当快且可逆,但会占用list 中介不必要的空间。

有没有办法在不先转换为列表的情况下将 numpy 的 unicode 字符数组与 Python 字符串相互转换?

事后思考

我可以让arr 显示为单个字符串,类似

buf = arr.view(dtype='U' + str(arr.size))

这会产生一个包含整个原始元素的 1 元素数组。反过来也是可能的:

buf.view(dtype='U1')

唯一的问题是结果的类型是np.str_,而不是str

【问题讨论】:

  • mystr = ''.join(arr.tolist()) 运行吗? arr 不需要是字符串数组或类似的东西吗?
  • 怎么样:np.frombuffer(mystr,dtype=np.uint8)?
  • @DanielMesejo。抱歉,我删除了 .view(np.uint32) 部分,因为它无关紧要。
  • @Divakar。我以为我已经尝试过了,但让我仔细检查一下
  • ''.join(arr)有问题吗?

标签: python arrays string numpy


【解决方案1】:

fromiter 有效,但速度很慢,因为它通过迭代器协议。将数据编码为 UTF-32(按系统字节顺序)并使用 numpy.frombuffer 会快得多:

In [56]: x = ''.join(chr(random.randrange(0x0fff)) for i in range(1000))

In [57]: codec = 'utf-32-le' if sys.byteorder == 'little' else 'utf-32-be'

In [58]: %timeit numpy.frombuffer(bytearray(x, codec), dtype='U1')
2.79 µs ± 47 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [59]: %timeit numpy.fromiter(x, dtype='U1', count=len(x))
122 µs ± 3.82 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [60]: numpy.array_equal(numpy.fromiter(x, dtype='U1', count=len(x)), numpy.fr
    ...: ombuffer(bytearray(x, codec), dtype='U1'))
Out[60]: True

我使用sys.byteorder 来确定是编码为utf-32-le 还是utf-32-be。此外,使用bytearray 而不是encode 会得到一个可变字节数组而不是一个不可变字节对象,因此生成的数组是可写的。


至于反向转换,arr.view(dtype=f'U{arr.size}')[0] 可以工作,但使用item() 会更快一些,并且会生成一个普通的字符串对象,从而避免numpy.str_ 的行为与str 不太一样的奇怪边缘情况:

In [72]: a = numpy.frombuffer(bytearray(x, codec), dtype='U1')

In [73]: type(a.view(dtype=f'U{a.size}')[0])
Out[73]: numpy.str_

In [74]: type(a.view(dtype=f'U{a.size}').item())
Out[74]: str

In [75]: %timeit a.view(dtype=f'U{a.size}')[0]
3.63 µs ± 34 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [76]: %timeit a.view(dtype=f'U{a.size}').item()
2.14 µs ± 23.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

最后,请注意 NumPy 不像普通 Python 字符串对象那样处理空值。 NumPy 无法区分 'asdf\x00\x00\x00''asdf',因此如果您的数据可能包含空代码点,则使用 NumPy 数组进行字符串操作是不安全的。

【讨论】:

  • 我很确定U1 代表系统字节顺序。就我而言,它转换为<U1,但实际的dtype 将byteorder 设置为=
  • @MadPhysicist:我的印象是><U dtype 中控制了代码点相对于彼此的顺序,但显然它控制代码中的字节顺序点。
  • 我什至不知道item 的存在。绝对选择这个答案而不是我的答案。
  • 我已将arr = np.array([mystr]).view(dtype='U1') 添加到我的答案中。它似乎比任何其他选项都快得多。这次我确实检查了单位。在我看来,这在概念上与 .item() 相比更好,这很好。
  • @MadPhysicist:哦,对,这是一个选项。我被复杂性分散了注意力,完全忘记了你可以直接将字符串传递给 NumPy。
【解决方案2】:

我发现将字符串转换为数组的最快方法是

arr = np.array([mystr]).view(dtype='U1')

另一种(较慢)基于@Daniel Mesejo's comment 将字符串转换为 unicode 代码点数组的方法:

arr = np.fromiter(mystr, dtype='U1', count=len(mystr))

查看fromiter 的源代码表明,将count 参数设置为字符串的长度将导致整个数组一次分配,而不是执行多次重新分配。

要转换回字符串:

str(arr.view(dtype=f'U{arr.size}')[0])

在大多数情况下,不需要最终转换为 Python str,因为 np.str_str 的子类。

arr.view(dtype=f'U{arr.size}')[0]

附录:frombufferarray 的时序

100

mystr = ''.join(chr(random.choice(range(1, 0x1000))) for _ in range(100))

%timeit np.array([mystr]).view(dtype='U1')
1.43 µs ± 27.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit np.frombuffer(bytearray(mystr, 'utf-32-le'), dtype='U1')
1.2 µs ± 9.06 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

10000

mystr = ''.join(chr(random.choice(range(1, 0x1000))) for _ in range(10000))

%timeit np.array([mystr]).view(dtype='U1')
4.33 µs ± 13.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit np.frombuffer(bytearray(mystr, 'utf-32-le'), dtype='U1')
10.9 µs ± 29.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

1000000

mystr = ''.join(chr(random.choice(range(1, 0x1000))) for _ in range(1000000))

%timeit np.array([mystr]).view(dtype='U1')
672 µs ± 1.64 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit np.frombuffer(bytearray(mystr, 'utf-32-le'), dtype='U1')
732 µs ± 5.22 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

【讨论】:

  • 这不是更好的缩放。这是 53.4 毫秒,而 733 微秒
  • @user2357112。我会看到自己出来的。去一所好学校。我可以在哪里学习阅读。
  • 我自己也犯过很多次同样的错误。
猜你喜欢
  • 1970-01-01
  • 2016-03-10
  • 2013-05-05
  • 1970-01-01
  • 2011-07-18
  • 1970-01-01
  • 2019-06-13
  • 2021-04-26
  • 2015-03-28
相关资源
最近更新 更多