2009-04-22 16 views
11

La función struct.pack() permite convertir enteros de hasta 64 bits a cadenas de bytes. ¿Cuál es la forma más eficiente de empacar un número entero aún mayor? Prefiero no agregar una dependencia en módulos no estándar como PyCrypto (que proporciona num_to_bytes()).Empaquetado de enteros de tamaño arbitrario eficiente en Python

+2

No estoy seguro de lo que está hablando. ¿Quieres poner la versión de cadena de un Python en una estructura? La versión de cadena de un largo es una cadena; lo empacas como cualquier otra cuerda. ¿Cuál es tu verdadera pregunta? –

+3

El OP desea empacar, de la manera más eficiente posible, un entero de tamaño arbitrario en una representación razonable de cadenas de bytes. –

Respuesta

1

Según lo sugerido por S.Lott en un comentario, simplemente convierta el número en una cadena y empaquete esa cadena. Por ejemplo,

x = 2 ** 12345 
struct.pack("40s", str(x)) 
+1

Eso es incluso menos eficiente que simplemente almacenar la representación de cadena del entero, debido al relleno de int con bytes nulos. –

+0

La pregunta no está clara qué tipo de eficiencia se requiere? ¿Estamos buscando el uso más eficiente del espacio? ¿O quizás el menor tiempo de procesamiento para empacar la estructura? Para el caso, ¿la eficiencia es incluso un gran problema? La pregunta fue la respuesta más eficiente, pero las personas a menudo optimizan prematuramente. –

3

Suponiendo que el cartel paquete de un entero grande como una cadena binaria, es decir, no utilizar un byte de almacenamiento por dígito del número. Una forma de hacer esto parece ser:

import marshal 

a = 47L 
print marshal.dumps(a) 

Esta impresora:

'l\x01\x00\x00\x00/\x00' 

No puedo decir que entiendo cómo interpretar estos bits, en este momento ...

+0

Eso va en la dirección correcta, pero todavía tiene dos bytes redundantes en el frente. –

+0

@Mike: en realidad, más que eso: creo que la "l" y los primeros 4 dígitos son solo un recuento del contenido, seguidos por un byte único ("/" == chr (47)) y un nulo al final. También parece que Marshal está haciendo un esquema de codificación más complejo que solo incluye los bytes sin procesar (vea los volcados (2 ** 64-1) por ejemplo y no los bytes 0x7f en el medio. – Brian

1

I tómelo, ¿quiere decir que solo quiere usar tantos bytes como necesite para representar el número? p.ej. si el número es:

  • 255 o menos tendrá que utilizar solamente 1 byte
  • 65535 o menos 2 bytes
  • 16777215 o menos 3 bytes
  • , etc, etc

Por Psion PDA por lo general tienen algunos de los planes de empaque en los que lee el primer byte, detecta si tiene el bit más alto establecido y luego lee otro byte si lo tiene. De esta forma, seguirías leyendo los bytes hasta que leas el número "completo". Ese sistema funciona bastante bien si la mayoría de los números con los que se trata son bastante pequeños, ya que normalmente solo usará uno o dos bytes por número.

La alternativa es tener uno (o más) bytes que representen la cantidad total de bytes utilizados, pero en ese punto, básicamente, es una cadena en Python. es decir, es una cadena de dígitos base-256.

5

Qué quiere decir algo así:

def num_to_bytes(num): 
    bytes = [] 
    num = abs(num) # Because I am unsure about negatives... 
    while num > 0: 
     bytes.append(chr(num % 256)) 
     num >>= 8 
    return ''.join(reversed(bytes)) 

def bytes_to_num(bytes): 
    num = 0 
    for byte in bytes: 
     num <<= 8 
     num += ord(byte) 
    return num 

for n in (1, 16, 256, 257, 1234567890987654321): 
    print n, 
    print num_to_bytes(n).encode('hex'), 
    print bytes_to_num(num_to_bytes(n)) 

que devuelve:

1 01 1 
16 10 16 
256 0100 256 
257 0101 257 
1234567890987654321 112210f4b16c1cb1 1234567890987654321 

que no estoy seguro de qué hacer con los negativos ... No soy tan familiarizados con un poco de twidling

EDIT: Otra solución (que se extiende alrededor del 30% más rápido por mis pruebas):

def num_to_bytes(num): 
    num = hex(num)[2:].rstrip('L') 
    if len(num) % 2: 
     return ('0%s' % num).decode('hex') 
    return num.decode('hex') 

def bytes_to_num(bytes): 
    return int(bytes.encode('hex'), 16) 
+0

Sí, esto es lo que quiero decir, y Escribí tal función hace un tiempo. Sin embargo, quería algo que sea más fácil de "portar", como un truco de comprensión de listas o (mejor) una función de biblioteca que simplemente no sabía. – noamtm

+0

Acabo de publicar otra publicación con built-in transcodificación int/hex y transconding hex/str ... ¡También va un poco más rápido! –

+0

@noamtm: Actualice la pregunta con hechos adicionales. No solo oculte información en los comentarios. –

1

Esto es un poco hacky, pero se puede ir a través de la representación de cadena hexadecimal, y no a binario con el códec hexagonal:

>>> a = 2**60 
>>> a 
1152921504606846976L 
>>> hex(a) 
'0x1000000000000000L' 
>>> hex(a).rstrip("L")[2:].decode('hex') 
'\x10\x00\x00\x00\x00\x00\x00\x00'  # 8bytes, as expected. 

>>> int(_.encode('hex'), 16) 
1152921504606846976L 

se rompe un poco porque el códec hexagonal requiere un número par de dígitos, por lo que tendrá a la almohadilla para eso, y usted tendrá que establecer un indicador para manejar números negativos.He aquí un paquete genérico/deshacer las maletas:

def pack(num): 
    if num <0: 
     num = (abs(num) << 1) | 1 # Hack - encode sign as lowest bit. 
    else: 
     num = num << 1 
    hexval = hex(num).rstrip("L")[2:] 
    if len(hexval)%2 ==1: hexval = '0' + hexval 
    return hexval.decode('hex') 

def unpack(s): 
    val = int(s.encode('hex'), 16) 
    sign = -1 if (val & 1) else 1 
    return sign * (val>>1) 


for i in [10,4534,23467, 93485093485, 2**50, 2**60-1, -1, -20, -2**60]: 
    assert unpack(pack(i)) == i 

Con todo el tocar el violín para el relleno etc requerido, no estoy seguro de que es mucho mejor que una solución hecha a mano sin embargo.

Cuestiones relacionadas