2016-08-26 5 views
0

Python und wc nicht einverstanden sind drastisch auf die Byte-Anzahl (Länge) einer bestimmten Zeichenfolge:Warum stimmen Python und wc bei der Byteanzahl nicht überein?

with open("commedia.pfc", "w") as f: 
    t = ''.join(chr(int(b, base=2)) for b in chunks(compressed, 8)) 
    print(len(t)) 
    f.write(t) 

Output : 318885 

$> wc commedia.pfc 
    2181 12282 461491 commedia.pfc 

Die Datei meist nicht lesbarer Zeichen gemacht wird, so dass ich eine hexdump bieten:

http://www.filedropper.com/dump_2

Die Datei ist das Ergebnis einer Präfix-freien Komprimierung, wenn Sie fragen, kann ich die vollständige zur Verfügung stellen Code, der es zusammen mit dem Eingabetext generiert.

Warum sind beide Byte-Zählungen nicht gleich?


ich hinzufügen den vollständigen Code des Komprimierungsalgorithmus, es lange aussieht, aber voller Dokumentation und Tests ist, sollte zu verstehen, so einfach sein:

""" 
Implementation of prefix-free compression and decompression. 
""" 
import doctest 
from itertools import islice 
from collections import Counter 
import random 
import json 

def binary_strings(s): 
    """ 
    Given an initial list of binary strings `s`, 
    yield all binary strings ending in one of `s` strings. 

    >>> take(9, binary_strings(["010", "111"])) 
    ['010', '111', '0010', '1010', '0111', '1111', '00010', '10010', '01010'] 
    """ 
    yield from s 
    while True: 
     s = [b + x for x in s for b in "01"] 
     yield from s 

def take(n, iterable): 
    """ 
    Return first n items of the iterable as a list. 
    """ 
    return list(islice(iterable, n)) 

def chunks(xs, n, pad='0'): 
    """ 
    Yield successive n-sized chunks from xs. 
    """ 
    for i in range(0, len(xs), n): 
     yield xs[i:i + n] 

def reverse_dict(dictionary): 
    """ 
    >>> sorted(reverse_dict({1:"a",2:"b"}).items()) 
    [('a', 1), ('b', 2)] 
    """ 
    return {value : key for key, value in dictionary.items()} 

def prefix_free(generator): 
    """ 
    Given a `generator`, yield all the items from it 
    that do not start with any preceding element. 

    >>> take(6, prefix_free(binary_strings(["00", "01"]))) 
    ['00', '01', '100', '101', '1100', '1101'] 
    """ 
    seen = [] 
    for x in generator: 
     if not any(x.startswith(i) for i in seen): 
      yield x 
      seen.append(x) 

def build_translation_dict(text, starting_binary_codes=["000", "100","111"]): 
    """ 
    Builds a dict for `prefix_free_compression` where 
     More common char -> More short binary strings 
    This is compression as the shorter binary strings will be seen more times than 
    the long ones. 

    Univocity in decoding is given by the binary_strings being prefix free. 

    >>> sorted(build_translation_dict("aaaaa bbbb ccc dd e", ["01", "11"]).items()) 
    [(' ', '001'), ('a', '01'), ('b', '11'), ('c', '101'), ('d', '0001'), ('e', '1001')] 
    """ 
    binaries = sorted(list(take(len(set(text)), prefix_free(binary_strings(starting_binary_codes)))), key=len) 
    frequencies = Counter(text) 
    # char value tiebreaker to avoid non-determinism      v 
    alphabet = sorted(list(set(text)), key=(lambda ch: (frequencies[ch], ch)), reverse=True) 
    return dict(zip(alphabet, binaries)) 

def prefix_free_compression(text, starting_binary_codes=["000", "100","111"]): 
    """ 
    Implements `prefix_free_compression`, simply uses the dict 
    made with `build_translation_dict`. 

    Returns a tuple (compressed_message, tranlation_dict) as the dict is needed 
    for decompression. 

    >>> prefix_free_compression("aaaaa bbbb ccc dd e", ["01", "11"])[0] 
    '010101010100111111111001101101101001000100010011001' 
    """ 
    translate = build_translation_dict(text, starting_binary_codes) 
    # print(translate) 
    return ''.join(translate[i] for i in text), translate 

def prefix_free_decompression(compressed, translation_dict): 
    """ 
    Decompresses a prefix free `compressed` message in the form of a string 
    composed only of '0' and '1'. 

    Being the binary codes prefix free, 
    the decompression is allowed to take the earliest match it finds. 

    >>> message, d = prefix_free_compression("aaaaa bbbb ccc dd e", ["01", "11"]) 
    >>> message 
    '010101010100111111111001101101101001000100010011001' 
    >>> sorted(d.items()) 
    [(' ', '001'), ('a', '01'), ('b', '11'), ('c', '101'), ('d', '0001'), ('e', '1001')] 
    >>> ''.join(prefix_free_decompression(message, d)) 
    'aaaaa bbbb ccc dd e' 
    """ 
    decoding_translate = reverse_dict(translation_dict) 
    # print(decoding_translate) 
    word = '' 
    for bit in compressed: 
     # print(word, "-", bit) 
     if word in decoding_translate: 
      yield decoding_translate[word] 
      word = '' 
     word += bit 
    yield decoding_translate[word] 


if __name__ == "__main__": 
    doctest.testmod() 
    with open("commedia.txt") as f: 
     text = f.read() 
    compressed, d = prefix_free_compression(text) 
    with open("commedia.pfc", "w") as f: 
     t = ''.join(chr(int(b, base=2)) for b in chunks(compressed, 8)) 
     print(len(t)) 
     f.write(t) 
    with open("commedia.pfcd", "w") as f: 
     f.write(json.dumps(d)) 
    # dividing by 8 goes from bit length to byte length 
    print("Compressed/uncompressed ratio is {}".format((len(compressed)//8)/len(text))) 
    original = ''.join(prefix_free_decompression(compressed, d)) 
    assert original == text 

commedia.txt filedropper.com/commedia ist

+0

Ja bitte, könnten Sie den Eingabetext und den Code zur Verfügung stellen? –

+0

@ Tiger-222 Eingabedatei zum Algorithmus ist: http://www.filedropper.com/commedia – Caridorc

+0

@ Tiger-222 Ich habe den vollständigen Code hinzugefügt, falls es benötigt wird – Caridorc

Antwort

4

Sie verwenden Python3 und ein str Objekt - das heißt, die Anzahl, die Sie in len(t) sehen, ist die Anzahl Zeichen in der Zeichenfolge. Jetzt sind Zeichen keine Bytes - and it is so since the 90's.

Da Sie keine explizite Textkodierung deklariert haben, kodiert die Datei Ihren Text mit der Systemstandardkodierung - die unter Linux oder Mac OS X utf-8 sein wird - einer Kodierung, in der alle Zeichen fallen, die nicht enthalten sind Der ASCII-Bereich (ord (ch)> 127) verwendet mehr als ein Byte auf der Festplatte.

Also, Ihr Programm ist grundsätzlich falsch. Definieren Sie zunächst, ob es sich um Text oder Bytes handelt. Wenn Sie mit Bytes dealign, öffnen Sie die Datei in Binär-Modus writting (wb, nicht w) und ändern Sie diese Zeile:

t = ''.join(chr(int(b, base=2)) for b in chunks(compressed, 8)) 

zu

t = bytes((int(b, base=2) for b in chunks(compressed, 8)) 

Auf diese Weise ist es klar, dass Sie arbeiten, mit den Bytes selbst, und nicht Mangelnde Zeichen und Bytes.

Natürlich gibt es eine hässliche Problemumgehung, um eine "transparente Codierung" des Textes zu machen, den Sie zu einem Bytes-Objekt hatten - (wenn Ihre ursprüngliche Liste alle Zeichencodepunkte im Bereich 0-256 hätte) könnte Ihr vorheriges t mit latin1 kodieren, bevor Sie es in eine Datei schreiben. Aber das wäre semantisch falsch gewesen. Sie können auch mit Pythons wenig bekanntem "Bytearray" -Objekt experimentieren: es gibt einem die Möglichkeit, mit Elementen umzugehen, die 8-Bit-Zahlen sind, und die Bequemlichkeit, veränderlich und erweiterbar zu sein (genau wie ein C "String") haben Sie genügend Speicherplatz zugewiesen)

+0

Das Ersetzen der Zeile, wie Sie vorgeschlagen haben, behebt das Problem, jetzt gibt 'wc' die korrekte Byteanzahl von '318885' als Python. Kurz gesagt, die Verwendung von Zeichen anstelle von Bytes verschwendet wegen der "ineffizienten" (modernen) Codierung fast 150000 Bytes. Gut zu wissen. (und danke für die umfassende Antwort :)) – Caridorc

+1

Nein - die Verwendung von Zeichen anstelle von Bytes wurde aufgezeichnet ** falsche ** Ergebnisse - nicht was Sie wnated. In utf-8 ist nichts von Vorteil - wenn Sie sich selbst auf nicht akzentuierte lateinische Zeichen beschränken, hat Text die gleiche Größe wie ASCII - ibut dieselbe Datei kann Text in den meisten bekannten menschlichen Sprachen und Skripten aufzeichnen, wobei mehr als ein Byte verwendet wird pro Zeichen. Was Sie jedoch aufzeichnen, sind Zahlen von 0 bis 255, und sie wurden als zufällige Zeichen in Ihrer Datei aufgezeichnet. – jsbueno

0

@jsbueno ist richtig.Außerdem lesen Modus, wenn Sie die resultierende Datei in binär öffnen, erhalten Sie das gute Ergebnis:

>>> with open('commedia.pfc', 'rb') as f: 
>>> print(len(f.read())) 
461491 
Verwandte Themen