Andmete tihendamine Huffmani algoritmiga

Kanne

Selles artiklis räägin tuntud Huffmani algoritmist ja selle rakendamisest andmete tihendamisel.

Selle tulemusena kirjutame lihtsa arhiivi. See on juba olnud artikkel Habré kohta, kuid ilma praktilise rakendamiseta. Praeguse postituse teoreetiline materjal on võetud kooli informaatika tundidest ja Robert Laforeti raamatust "Andmestruktuurid ja algoritmid Javas". Niisiis, kõik on alla!

Väike mõtisklus

Tavalises tekstifailis on üks märk kodeeritud 8 bitiga (ASCII kodeering) või 16 bitiga (Unicode kodeering). Järgmisena käsitleme ASCII-kodeeringut. Näiteks võtke string s1 = "SUSIE ÜTLES, ET ON LIHTNE". Kokku on real loomulikult 22 märki, sealhulgas tühikud ja reavahetus - 'n'. Seda rida sisaldav fail kaalub 22*8 = 176 bitti. Kohe tekib küsimus: kas on ratsionaalne kasutada 8 märgi kodeerimiseks kõiki 1 bitti? Me ei kasuta kõiki ASCII-märke. Isegi kui need oleksid, oleks ratsionaalsem anda kõige sagedasemale tähele - S - lühim võimalik kood ja kõige haruldasema tähe korral - T (või U või "n") - anda kood autentsem. See on Huffmani algoritm: peate leidma parima kodeerimisvaliku, milles fail on minimaalse kaaluga. On täiesti normaalne, et erinevatel tähemärkidel on erinev koodi pikkus – see on algoritmi aluseks.

Kodeerimine

Miks mitte anda tähemärgile 'S' näiteks 1 biti pikkune kood: 0 või 1. Olgu see 1. Seejärel anname teiseks enimesinevaks märgiks - ' ' (tühikuks) - 0. Kujutage ette, te hakkasite dekodeerige oma sõnum - kodeeritud string s1 - ja näete, et kood algab 1-ga. Mida teha: kas see on märk S või mõni muu märk, näiteks A? Seetõttu on oluline reegel:

Ükski kood ei tohi olla teise eesliide

See reegel on algoritmi võti. Seetõttu algab koodi loomine sagedustabeliga, mis näitab iga sümboli sagedust (esinemiste arvu):

Andmete tihendamine Huffmani algoritmiga Kõige rohkem esinevaid märke tuleks kodeerida kõige vähemate esinemissagedustega võimalik bittide arv. Toon näite ühest võimalikust kooditabelist:

Andmete tihendamine Huffmani algoritmiga Seega näeb kodeeritud sõnum välja järgmine:

10 01111 10 110 1111 00 10 010 1110 10 00 110 0110 00 110 10 00 1111 010 10 1110 01110

Eraldasin iga märgi koodi tühikuga. Tihendatud failis seda tegelikult ei juhtu!
Tekib küsimus: kuidas see uustulnuk välja mõtles koodi, kuidas kooditabelit luua? Seda arutatakse allpool.

Huffmani puu ehitamine

Siin tulevad appi binaarsed otsingupuud. Ärge muretsege, te ei vaja siin otsimise, sisestamise ja kustutamise meetodeid. Siin on Java puu struktuur:

public class Node {
    private int frequence;
    private char letter;
    private Node leftChild;
    private Node rightChild;
    ...
}

class BinaryTree {
    private Node root;

    public BinaryTree() {
        root = new Node();
    }
    public BinaryTree(Node root) {
        this.root = root;
    }
    ...
}

See ei ole täielik kood, täielik kood on allpool.

Siin on puu ehitamise algoritm:

  1. Looge sõnumist iga tähemärgi jaoks sõlmeobjekt (rida s1). Meie puhul on 9 sõlme (sõlmeobjektid). Iga sõlm koosneb kahest andmeväljast: sümbolist ja sagedusest
  2. Looge iga sõlme sõlme jaoks puuobjekt (BinaryTree). Sõlmest saab puu juur.
  3. Sisestage need puud prioriteetsesse järjekorda. Mida madalam on sagedus, seda kõrgem on prioriteet. Seega valitakse ekstraheerimisel alati madalaima sagedusega puu.

Järgmisena peate tsükliliselt tegema järgmist.

  1. Tooge prioriteetjärjekorrast kaks puud ja tehke need uue sõlme (vastloodud ilma täheta sõlm) järglasteks. Uue sõlme sagedus on võrdne kahe järeltulija puu sageduste summaga.
  2. Selle sõlme jaoks looge selle sõlme juurtega puu. Sisestage see puu tagasi prioriteetsesse järjekorda. (Kuna puul on uus sagedus, satub see suure tõenäosusega järjekorras uude kohta)
  3. Jätkake samme 1 ja 2, kuni järjekorda jääb üks puu – Huffmani puu

Mõelge sellele algoritmile real s1:

Andmete tihendamine Huffmani algoritmiga

Siin tähistab sümbol "lf" (reavahetus) uut rida, "sp" (tühik) on tühik.

Ja mis edasi?

Meil on Huffmani puu. OKEI. Ja mida sellega teha? Nad ei võta seda tasuta. Ja siis peate jälgima kõiki võimalikke teid puu juurest lehtedeni. Oleme nõus märgistama serva 0, kui see viib vasaku lapseni, ja 1, kui see viib paremasse. Rangelt võttes on nendes tähistes märgikood tee puu juurest sama märki sisaldava leheni.

Andmete tihendamine Huffmani algoritmiga

Seega selgus koodide tabel. Pange tähele, et kui me seda tabelit arvesse võtame, saame järeldada iga märgi "kaalu" kohta - see on selle koodi pikkus. Seejärel kaalub lähtefail tihendatud kujul: 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 bitti . Alguses kaalus see 176 bitti. Seetõttu vähendasime seda koguni 176/65 = 2.7 korda! Aga see on utoopia. Sellist suhet tõenäoliselt ei saavutata. Miks? Seda arutatakse veidi hiljem.

Dekodeerimine

Noh, võib-olla on kõige lihtsam asi dekodeerimine. Arvan, et paljud teist on arvanud, et tihendatud faili on võimatu lihtsalt luua ilma vihjeteta selle kodeerimise kohta – me ei saa seda dekodeerida! Jah, jah, mul oli sellest raske aru saada, kuid ma pean looma tihendustabeliga tekstifaili table.txt:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

Tabeli sisestus kujul 'sümbol' 'märgikood'. Miks on 01110 ilma sümbolita? Tegelikult on see sümboliga, lihtsalt java tööriistad, mida ma faili väljastamisel kasutan, teisendatakse reavahetusmärk - 'n' - reavahetuseks (ükskõik kui tobedalt see ka ei kõlaks). Seetõttu on ülaosas tühi rida koodi 01110 märk. Koodi 00 puhul on märk rea alguses tühik. Ütlen kohe, et meie Khani koefitsiendi puhul võib see tabeli salvestamise meetod väita olevat kõige irratsionaalsem. Kuid seda on lihtne mõista ja rakendada. Mul on hea meel kuulda teie soovitusi optimeerimise kohta kommentaarides.

Selle tabeliga on seda väga lihtne dekodeerida. Meenutagem, millisest reeglist lähtusime kodeeringu loomisel:

Ükski kood ei tohi olla teise eesliide

Siin mängib see hõlbustavat rolli. Loeme bittide kaupa järjestikku ja niipea, kui saadud loetud bittidest koosnev string d ühtib märgimärgile vastava kodeeringuga, saame kohe teada, et märgimärk (ja ainult see!) oli kodeeritud. Järgmisena kirjutame dekodeerimisstringi (dekodeeritud sõnumit sisaldavasse stringi) märgi, lähtestame d-stringi ja loeme kodeeritud faili edasi.

Реализация

On aeg oma koodi alandada, kirjutades arhiivi. Nimetagem seda kompressoriks.

Alusta uuesti. Kõigepealt kirjutame klassi Node:

public class Node {
    private int frequence;//частота
    private char letter;//буква
    private Node leftChild;//левый потомок
    private Node rightChild;//правый потомок

   

    public Node(char letter, int frequence) { //собственно, конструктор
        this.letter = letter;
        this.frequence = frequence;
    }

    public Node() {}//перегрузка конструтора для безымянных узлов(см. выше в разделе о построении дерева Хаффмана)
    public void addChild(Node newNode) {//добавить потомка
        if (leftChild == null)//если левый пустой=> правый тоже=> добавляем в левый
            leftChild = newNode;
        else {
            if (leftChild.getFrequence() <= newNode.getFrequence()) //в общем, левым потомком
                rightChild = newNode;//станет тот, у кого меньше частота
            else {
                rightChild = leftChild;
                leftChild = newNode;
            }
        }

        frequence += newNode.getFrequence();//итоговая частота
    }

    public Node getLeftChild() {
        return leftChild;
    }

    public Node getRightChild() {
        return rightChild;
    }

    public int getFrequence() {
        return frequence;
    }

    public char getLetter() {
        return letter;
    }

    public boolean isLeaf() {//проверка на лист
        return leftChild == null && rightChild == null;
    }
}

Nüüd puu:

class BinaryTree {
    private Node root;

    public BinaryTree() {
        root = new Node();
    }

    public BinaryTree(Node root) {
        this.root = root;
    }

    public int getFrequence() {
        return root.getFrequence();
    }

    public Node getRoot() {
        return root;
    }
}

Prioriteetne järjekord:

import java.util.ArrayList;//да-да, очередь будет на базе списка

class PriorityQueue {
    private ArrayList<BinaryTree> data;//список очереди
    private int nElems;//кол-во элементов в очереди

    public PriorityQueue() {
        data = new ArrayList<BinaryTree>();
        nElems = 0;
    }

    public void insert(BinaryTree newTree) {//вставка
        if (nElems == 0)
            data.add(newTree);
        else {
            for (int i = 0; i < nElems; i++) {
                if (data.get(i).getFrequence() > newTree.getFrequence()) {//если частота вставляемого дерева меньше 
                    data.add(i, newTree);//чем част. текущего, то cдвигаем все деревья на позициях справа на 1 ячейку                   
                    break;//затем ставим новое дерево на позицию текущего
                }
                if (i == nElems - 1) 
                    data.add(newTree);
            }
        }
        nElems++;//увеличиваем кол-во элементов на 1
    }

    public BinaryTree remove() {//удаление из очереди
        BinaryTree tmp = data.get(0);//копируем удаляемый элемент
        data.remove(0);//собственно, удаляем
        nElems--;//уменьшаем кол-во элементов на 1
        return tmp;//возвращаем удаленный элемент(элемент с наименьшей частотой)
    }
}

Klass, mis loob Huffmani puu:

public class HuffmanTree {
    private final byte ENCODING_TABLE_SIZE = 127;//длина кодировочной таблицы
    private String myString;//сообщение
    private BinaryTree huffmanTree;//дерево Хаффмана
    private int[] freqArray;//частотная таблица
    private String[] encodingArray;//кодировочная таблица


    //----------------constructor----------------------
    public HuffmanTree(String newString) {
        myString = newString;

        freqArray = new int[ENCODING_TABLE_SIZE];
        fillFrequenceArray();

        huffmanTree = getHuffmanTree();

        encodingArray = new String[ENCODING_TABLE_SIZE];
        fillEncodingArray(huffmanTree.getRoot(), "", "");
    }

    //--------------------frequence array------------------------
    private void fillFrequenceArray() {
        for (int i = 0; i < myString.length(); i++) {
            freqArray[(int)myString.charAt(i)]++;
        }
    }

    public int[] getFrequenceArray() {
        return freqArray;
    }

    //------------------------huffman tree creation------------------
    private BinaryTree getHuffmanTree() {
        PriorityQueue pq = new PriorityQueue();
        //алгоритм описан выше
        for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
            if (freqArray[i] != 0) {//если символ существует в строке
                Node newNode = new Node((char) i, freqArray[i]);//то создать для него Node
                BinaryTree newTree = new BinaryTree(newNode);//а для Node создать BinaryTree
                pq.insert(newTree);//вставить в очередь
            }
        }

        while (true) {
            BinaryTree tree1 = pq.remove();//извлечь из очереди первое дерево.

            try {
                BinaryTree tree2 = pq.remove();//извлечь из очереди второе дерево

                Node newNode = new Node();//создать новый Node
                newNode.addChild(tree1.getRoot());//сделать его потомками два извлеченных дерева
                newNode.addChild(tree2.getRoot());

                pq.insert(new BinaryTree(newNode);
            } catch (IndexOutOfBoundsException e) {//осталось одно дерево в очереди
                return tree1;
            }
        }
    }

    public BinaryTree getTree() {
        return huffmanTree;
    }

    //-------------------encoding array------------------
    void fillEncodingArray(Node node, String codeBefore, String direction) {//заполнить кодировочную таблицу
        if (node.isLeaf()) {
            encodingArray[(int)node.getLetter()] = codeBefore + direction;
        } else {
            fillEncodingArray(node.getLeftChild(), codeBefore + direction, "0");
            fillEncodingArray(node.getRightChild(), codeBefore + direction, "1");
        }
    }

    String[] getEncodingArray() {
        return encodingArray;
    }

    public void displayEncodingArray() {//для отладки
        fillEncodingArray(huffmanTree.getRoot(), "", "");

        System.out.println("======================Encoding table====================");
        for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
            if (freqArray[i] != 0) {
                System.out.print((char)i + " ");
                System.out.println(encodingArray[i]);
            }
        }
        System.out.println("========================================================");
    }
    //-----------------------------------------------------
    String getOriginalString() {
        return myString;
    }
}

Klass, mis kodeerib/dekodeerib:

public class HuffmanOperator {
    private final byte ENCODING_TABLE_SIZE = 127;//длина таблицы
    private HuffmanTree mainHuffmanTree;//дерево Хаффмана (используется только для сжатия)
    private String myString;//исходное сообщение
    private int[] freqArray;//частотаная таблица
    private String[] encodingArray;//кодировочная таблица
    private double ratio;//коэффициент сжатия 


    public HuffmanOperator(HuffmanTree MainHuffmanTree) {//for compress
        this.mainHuffmanTree = MainHuffmanTree;

        myString = mainHuffmanTree.getOriginalString();

        encodingArray = mainHuffmanTree.getEncodingArray();

        freqArray = mainHuffmanTree.getFrequenceArray();
    }

    public HuffmanOperator() {}//for extract;

    //---------------------------------------compression-----------------------------------------------------------
    private String getCompressedString() {
        String compressed = "";
        String intermidiate = "";//промежуточная строка(без добавочных нулей)
        //System.out.println("=============================Compression=======================");
        //displayEncodingArray();
        for (int i = 0; i < myString.length(); i++) {
            intermidiate += encodingArray[myString.charAt(i)];
        }
        //Мы не можем писать бит в файл. Поэтому нужно сделать длину сообщения кратной 8=>
        //нужно добавить нули в конец(можно 1, нет разницы)
        byte counter = 0;//количество добавленных в конец нулей (байта в полне хватит: 0<=counter<8<127)
        for (int length = intermidiate.length(), delta = 8 - length % 8; 
        		counter < delta ; counter++) {//delta - количество добавленных нулей
            intermidiate += "0";
        }
        
        //склеить кол-во добавочных нулей в бинарном предаствлении и промежуточную строку 
        compressed = String.format("%8s", Integer.toBinaryString(counter & 0xff)).replace(" ", "0") + intermidiate;
        		
        //идеализированный коэффициент
        setCompressionRatio();
        //System.out.println("===============================================================");
        return compressed;
    }
    
    private void setCompressionRatio() {//посчитать идеализированный коэффициент 
        double sumA = 0, sumB = 0;//A-the original sum
        for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
            if (freqArray[i] != 0) {
                sumA += 8 * freqArray[i];
                sumB += encodingArray[i].length() * freqArray[i];
            }
        }
        ratio = sumA / sumB;
    }

    public byte[] getBytedMsg() {//final compression
        StringBuilder compressedString = new StringBuilder(getCompressedString());
        byte[] compressedBytes = new byte[compressedString.length() / 8];
        for (int i = 0; i < compressedBytes.length; i++) {
                compressedBytes[i] = (byte) Integer.parseInt(compressedString.substring(i * 8, (i + 1) * 8), 2);
        }
        return compressedBytes;
    }
    //---------------------------------------end of compression----------------------------------------------------------------
    //------------------------------------------------------------extract-----------------------------------------------------
    public String extract(String compressed, String[] newEncodingArray) {
        String decompressed = "";
        String current = "";
        String delta = "";
        encodingArray = newEncodingArray;
        
        //displayEncodingArray();
        //получить кол-во вставленных нулей
        for (int i = 0; i < 8; i++) 
        	delta += compressed.charAt(i);
        int ADDED_ZEROES = Integer.parseInt(delta, 2);
       
        for (int i = 8, l = compressed.length() - ADDED_ZEROES; i < l; i++) {
            //i = 8, т.к. первым байтом у нас идет кол-во вставленных нулей
            current += compressed.charAt(i);
            for (int j = 0; j < ENCODING_TABLE_SIZE; j++) {
                if (current.equals(encodingArray[j])) {//если совпало
                    decompressed += (char)j;//то добавляем элемент
                    current = "";//и обнуляем текущую строку
                }
            }
        }

        return decompressed;
    }

    public String getEncodingTable() {
        String enc = "";
    	for (int i = 0; i < encodingArray.length; i++) {
        	if (freqArray[i] != 0) 
        		enc += (char)i + encodingArray[i] + 'n';
        }
    	return enc;
    }

    public double getCompressionRatio() {
        return ratio;
    }


    public void displayEncodingArray() {//для отладки
        System.out.println("======================Encoding table====================");
        for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
            //if (freqArray[i] != 0) {
                System.out.print((char)i + " ");
                System.out.println(encodingArray[i]);
            //}
        }
        System.out.println("========================================================");
    }
    }

Klass, mis hõlbustab faili kirjutamist:

import java.io.File;
import java.io.PrintWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Closeable;

public class FileOutputHelper implements Closeable {
    private File outputFile;
    private FileOutputStream fileOutputStream;

    public FileOutputHelper(File file) throws FileNotFoundException {
        outputFile = file;
        fileOutputStream = new FileOutputStream(outputFile);
    }

    public void writeByte(byte msg) throws IOException {
        fileOutputStream.write(msg);
    }

    public void writeBytes(byte[] msg) throws IOException {
        fileOutputStream.write(msg);
    }

    public void writeString(String msg) {
    	try (PrintWriter pw = new PrintWriter(outputFile)) {
    		pw.write(msg);
    	} catch (FileNotFoundException e) {
    		System.out.println("Неверный путь, или такого файла не существует!");
    	}
    }

    @Override
    public void close() throws IOException {
        fileOutputStream.close();
    }

    public void finalize() throws IOException {
        close();
    }
}

Klass, mis hõlbustab failist lugemist:

import java.io.FileInputStream;
import java.io.EOFException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;

public class FileInputHelper implements Closeable {
	private FileInputStream fileInputStream;
	private BufferedReader fileBufferedReader;
	
	public FileInputHelper(File file) throws IOException {
		fileInputStream = new FileInputStream(file);
		fileBufferedReader = new BufferedReader(new InputStreamReader(fileInputStream));
	}
	
	
    public byte readByte() throws IOException {
    	int cur = fileInputStream.read();
    	if (cur == -1)//если закончился файл
    		throw new EOFException();
    	return (byte)cur;
    }
    
    public String readLine() throws IOException {
    	return fileBufferedReader.readLine();
    }
    
    @Override
    public void close() throws IOException{
    	fileInputStream.close();
    }
}

Noh, ja põhiklass:

import java.io.File;
import java.nio.charset.MalformedInputException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Paths;
import java.util.List;
import java.io.EOFException;
public class Main {
	private static final byte ENCODING_TABLE_SIZE = 127;
	
    public static void main(String[] args) throws IOException {
        try {//указываем инструкцию с помощью аргументов командной строки
            if (args[0].equals("--compress") || args[0].equals("-c"))
                compress(args[1]);
            else if ((args[0].equals("--extract") || args[0].equals("-x"))
            		&& (args[2].equals("--table") || args[2].equals("-t"))) {
            	extract(args[1], args[3]);
            }
            else
                throw new IllegalArgumentException();
        } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException e) {
            System.out.println("Неверный формат ввода аргументов ");
            System.out.println("Читайте Readme.txt");
            e.printStackTrace();
        }
    }

	public static void compress(String stringPath) throws IOException {
        List<String> stringList;
        File inputFile = new File(stringPath);
        String s = "";
        File compressedFile, table;
        
        try {
            stringList = Files.readAllLines(Paths.get(inputFile.getAbsolutePath()));
        } catch (NoSuchFileException e) {
            System.out.println("Неверный путь, или такого файла не существует!");
            return;
        } catch (MalformedInputException e) {
        	System.out.println("Текущая кодировка файла не поддерживается");
        	return;
        }

        for (String item : stringList) {
            s += item;
            s += 'n';
        }

        HuffmanOperator operator = new HuffmanOperator(new HuffmanTree(s));

        compressedFile = new File(inputFile.getAbsolutePath() + ".cpr");
        compressedFile.createNewFile();
        try (FileOutputHelper fo = new FileOutputHelper(compressedFile)) {
        	fo.writeBytes(operator.getBytedMsg());
        }
        //create file with encoding table:
        
        table = new File(inputFile.getAbsolutePath() + ".table.txt");
        table.createNewFile();
        try (FileOutputHelper fo = new FileOutputHelper(table)) {
        	fo.writeString(operator.getEncodingTable());
        }
        
        System.out.println("Путь к сжатому файлу: " + compressedFile.getAbsolutePath());
        System.out.println("Путь к кодировочной таблице " + table.getAbsolutePath());
        System.out.println("Без таблицы файл будет невозможно извлечь!");
        
        double idealRatio = Math.round(operator.getCompressionRatio() * 100) / (double) 100;//идеализированный коэффициент
        double realRatio = Math.round((double) inputFile.length() 
        		/ ((double) compressedFile.length() + (double) table.length()) * 100) / (double)100;//настоящий коэффициент
        
        System.out.println("Идеализированный коэффициент сжатия равен " + idealRatio);
        System.out.println("Коэффициент сжатия с учетом кодировочной таблицы " + realRatio);
    }

    public static void extract(String filePath, String tablePath) throws FileNotFoundException, IOException {
        HuffmanOperator operator = new HuffmanOperator();
        File compressedFile = new File(filePath),
        	 tableFile = new File(tablePath),
        	 extractedFile = new File(filePath + ".xtr");
        String compressed = "";
        String[] encodingArray = new String[ENCODING_TABLE_SIZE];
        //read compressed file
        //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!check here:
        try (FileInputHelper fi = new FileInputHelper(compressedFile)) {
        	byte b;
        	while (true) {
        		b = fi.readByte();//method returns EOFException
        		compressed += String.format("%8s", Integer.toBinaryString(b & 0xff)).replace(" ", "0");
        	}
        } catch (EOFException e) {
        	
        }
        
        //--------------------
        
        //read encoding table:
        try (FileInputHelper fi = new FileInputHelper(tableFile)) {
        	fi.readLine();//skip first empty string
        	encodingArray[(byte)'n'] = fi.readLine();//read code for 'n'
        	while (true) {
        		String s = fi.readLine();
        		if (s == null)
        			throw new EOFException();
        		encodingArray[(byte)s.charAt(0)] = s.substring(1, s.length());        		
        	}
        } catch (EOFException ignore) {}
        
        extractedFile.createNewFile();
        //extract:
		try (FileOutputHelper fo = new FileOutputHelper(extractedFile)) {
			fo.writeString(operator.extract(compressed, encodingArray));
		}
		
		System.out.println("Путь к распакованному файлу " + extractedFile.getAbsolutePath());
    }
}

Peate faili readme.txt juhistega ise kirjutama 🙂

Järeldus

See on vist kõik, mida ma öelda tahtsin. Kui teil on midagi öelda minu ebakompetentsuse kohta koodi, algoritmi ja üldiselt optimeerimise osas, siis kirjutage julgelt. Kui ma ei ole midagi selgitanud, palun kirjutage ka. Tahaksin sinust kommentaarides kuulda!

PS

Jah, jah, ma olen ikka veel siin, sest ma ei unustanud koefitsienti. Stringi s1 puhul kaalub kodeerimistabel 48 baiti - palju rohkem kui algne fail ja nad ei unustanud ka lisanulle (lisatud nullide arv on 7) => tihendussuhe on väiksem kui üks: 176 /(65 + 48*8 + 7) = 0.38. Kui ka sina seda märkasid, siis pole lihtsalt näkku otsas. Jah, see rakendus on väikeste failide puhul äärmiselt ebaefektiivne. Aga mis juhtub suurte failidega? Faili suurused on palju suuremad kui kodeerimistabeli suurus. Siin töötab algoritm nii nagu peab! Näiteks selleks Fausti monoloog arhiveerija annab reaalse (mitte idealiseeritud) koefitsiendi 1.46 - peaaegu poolteist korda! Ja jah, fail pidi olema inglise keeles.

Allikas: www.habr.com

Lisa kommentaar