2009-04-22 6 views
11

Die Funktion struct.pack() ermöglicht das Konvertieren von Ganzzahlen mit bis zu 64 Bit in Byte-Strings. Was ist der effizienteste Weg, um eine noch größere Ganzzahl zu packen? Ich würde lieber keine Abhängigkeit von Nicht-Standard-Modulen wie PyCrypto (die num_to_bytes() bereitstellt) hinzufügen.Effizientes beliebiges Integer-Packen in voller Größe in Python

+2

Nicht sicher, wovon Sie sprechen. Möchten Sie die String-Version eines Python long in eine Struktur einfügen? Die String-Version von long ist eine Zeichenfolge. Sie packen es wie jede andere Saite. Was ist deine wahre Frage? –

+3

Das OP möchte so effizient wie möglich eine beliebig große Ganzzahl in eine sinnvolle Byte-String-Darstellung packen. –

Antwort

1

Wie von S.Lott in einem Kommentar vorgeschlagen, nur die Zahl in eine Zeichenfolge konvertieren und diese Zeichenfolge packen. Zum Beispiel

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

Das ist noch weniger effizient als nur die Zeichenfolgendarstellung der Ganzzahl zu speichern, da das int mit Nullbytes aufgefüllt wird. –

+0

Die Frage ist unklar, welche Art von Effizienz erforderlich ist? Suchen wir nach der effizientesten Raumnutzung? Oder vielleicht die geringste Verarbeitungszeit, um die Struktur zu packen? Ist Effizienz sogar eine große Sache? Die Frage wurde nach der effizientesten Antwort gestellt, aber die Leute optimieren oft vorzeitig. –

3

das Plakat Unter der Annahme, will eine große Ganzzahl als eine binäre Zeichenfolge packen, das heißt nicht ein Byte Speicher pro Ziffer der Nummer verwenden. Eine Möglichkeit, dies zu tun, scheint zu sein:

import marshal 

a = 47L 
print marshal.dumps(a) 

Diese Drucke:

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

Ich kann nicht sagen, dass ich verstehe, wie diese Bits zu interpretieren, jetzt ...

+0

Das geht in die richtige Richtung, aber es hat immer noch zwei redundante Bytes an der Front. –

+0

@Mike: Eigentlich mehr als das - ich denke, die "l" und ersten 4 Ziffern sind nur eine Zählung des Inhalts, gefolgt von einem einzelnen Byte ("/" == chr (47)) und eine Null am Ende. Es sieht auch so aus, als würde Marshal ein komplexeres Codierungsschema machen, das nur die rohen Bytes enthält (schauen Sie sich zum Beispiel Dumps (2 ** 64-1) an und nicht die 0x7f Bytes in der Mitte. – Brian

1

I nehmen Sie es, Sie meinen, Sie wollen nur so viele Bytes verwenden, wie Sie die Zahl darstellen müssen? z.B. wenn die Zahl:

  • 255 oder weniger würden Sie nur 1 Byte
  • 65535 oder weniger 2 Byte
  • 16777215 oder weniger 3 Bytes
  • etc etc

über die Verwendung Psion PDA haben sie normalerweise ein Packschema, in dem Sie das erste Byte lesen, erkennen, ob es das höchste Bit gesetzt hat und dann ein anderes Byte lesen, wenn es das hat. Auf diese Weise würden Sie einfach weiter Bytes lesen, bis Sie die "volle" Nummer lesen. Dieses System funktioniert ziemlich gut, wenn die meisten Zahlen, mit denen Sie es zu tun haben, ziemlich klein sind, da Sie normalerweise nur ein oder zwei Bytes pro Nummer verwenden.

Die Alternative besteht darin, ein (oder mehrere) Bytes zu haben, die die Anzahl der insgesamt verwendeten Bytes darstellen, aber zu diesem Zeitpunkt ist es in Python sowieso eine Zeichenfolge. d.h. es ist eine Folge von 256 Basisziffern.

5

Haben Sie etwas wie dies bedeuten:

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)) 

Welche zurück:

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

Ich bin nur nicht sicher, was Negative zu tun ... Ich bin nicht so vertraut mit etwas Twittern.

EDIT: Eine andere Lösung (die durch meine Tests etwa 30% schneller läuft):

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

Ja, das ist was ich meine, und Ich habe vor einiger Zeit eine solche Funktion geschrieben, aber ich wollte etwas, das einfacher zu portieren ist, wie einen Listenverständnistrick oder (besser) eine Bibliotheksfunktion, von der ich nichts wusste. – noamtm

+0

Ich habe gerade einen neuen Artikel mit eingebaut int/hex transcoding und hex/str transcondition ... Es geht auch ein bisschen schneller! –

+0

@noamtm: Bitte aktualisieren Sie die Frage mit zusätzlichen Fakten.Verbergen Sie nicht nur Informationen in Kommentaren. –

1

Das ist ein bisschen hacky, aber man konnte über die Hex-String-Darstellung gehen und dort mit Binär der hex-Codec:

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

Es bricht ein wenig, weil der hex-Codec eine gerade Anzahl von Ziffern erfordert, so dass Sie für das zu Pad benötigen, und Sie werden eine Flagge zu handhaben negative Zahlen festlegen müssen.Hier ist eine generische Packung/auspacken:

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 

Mit all das zum Auffüllen etc Hantieren erforderlich, ich bin nicht sicher, es ist viel besser als eine handgerollte Lösung though.

Verwandte Themen