我的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
。
答案 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])
在大多数情况下,由于str
是np.str_
的子类,因此无需最终转换为Python str
。
arr.view(dtype=f'U{arr.size}')[0]
附录:frombuffer
与array
的时间
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数组用于字符串操作并不安全。