Convertir arrays numpy de puntos de código hacia y desde cadenas

Tengo una larga cadena de Unicode:

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

Me gustaría verlo como una serie de puntos de código, por lo que en este momento, estoy haciendo lo siguiente:

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

Me gustaría poder manipular la cadena como números y, finalmente, recuperar algunos puntos de código diferentes. Ahora me gustaría invertir la transformación:

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

Estas transformaciones son razonablemente rápidas e invertibles, pero ocupan una cantidad innecesaria de espacio con el intermediario de la list .

¿Hay alguna forma de convertir una matriz numpy de caracteres Unicode hacia y desde una cadena de Python sin convertir primero a una lista?

Pensamientos posteriores

Puedo obtener arr para aparecer como una sola cuerda con algo como

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

Esto da como resultado una matriz de 1 elemento que contiene el original completo. Lo inverso también es posible:

 buf.view(dtype='U1') 

El único problema es que el tipo del resultado es np.str_ , no str .

fromiter funciona, pero es muy lento, ya que pasa por el protocolo del iterador. Es mucho más rápido codificar sus datos a UTF-32 (en orden de bytes del sistema) y usar 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 

He usado sys.byteorder para determinar si codificar en utf-32-le o utf-32-be . Además, al utilizar bytearray lugar de encode obtiene un bytearray mutable en lugar de un objeto de bytes inmutables, por lo que la matriz resultante se puede escribir.


En cuanto a la conversión inversa, arr.view(dtype=f'U{arr.size}')[0] funciona, pero usar item() es un poco más rápido y produce un objeto de cadena normal, evitando posibles casos de bordes numpy.str_ no se comporta como 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) 

Finalmente, tenga en cuenta que NumPy no maneja nulos como lo hacen los objetos de cadena de Python normales. NumPy no puede distinguir entre 'asdf\x00\x00\x00' y 'asdf' , por lo que el uso de matrices NumPy para operaciones de cadenas no es seguro si sus datos pueden contener puntos de código nulo.

La forma más rápida que he encontrado para convertir una cadena a una matriz es

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

Otra forma (más lenta) de convertir una cadena en una matriz de puntos de código Unicode basada en el comentario de @Daniel Mesejo :

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

Al fromiter el código fuente de fromiter muestra que establecer el parámetro de count a la longitud de la cadena hará que la matriz completa se asigne de una vez, en lugar de realizar varias reasignaciones.

Para convertir de nuevo a una cadena:

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

Para la mayoría de los propósitos, la conversión final a str Python no es necesaria ya que np.str_ es una subclase de str .

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

Apéndice: Tiempo de frombuffer vs 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)