在字符串之间转换代码点的numpy数组

时间:2019-01-29 15:31:05

标签: python arrays string numpy

我的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

2 个答案:

答案 0 :(得分: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])

在大多数情况下,由于strnp.str_的子类,因此无需最终转换为Python 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)

答案 1 :(得分:2)

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字符串对象那样处理null。 NumPy无法区分'asdf\x00\x00\x00''asdf',因此,如果您的数据可能包含空代码点,则将NumPy数组用于字符串操作并不安全。