მონაცემთა ლეკუმლვა ჰაჀმანის ალგორითმით

Entry

ამ სტატიალი ვისაუბრებ ჰაჀმანის ცნობილ ალგორითმზე, ასევე მის გამოყენებაზე მონაცემთა ლეკუმლვისას.

ჹედეგად, ჩვენ დავწერთ მარტივ არქივს. ეს უკვე იყო სტატია ჰაბრეზე, მაგრამ პრაქტიკული განხორციელების გარეჹე. მიმდინარე პოსტის თეორიული მასალა ა჊ებულია სკოლის კომპიუტერული მეცნიერების გაკვეთილებიდან და რობერტ ლაჀორეტის წიგნიდან „მონაცემთა სტრუქტურები და ალგორითმები ჯავალი“. ასე რომ, ყველაჀერი მოჭრილია!

პატარა ანარეკლი

ჩვეულებრივ ტექსტურ Ⴠაილლი ერთი სიმბოლო დალიჀრულია 8 ბიტით (ASCII კოდირება) ან 16 (Unicode კოდირება). ჹემდეგი, განვიხილავთ ASCII კოდირებას. მაგალითად, აიჩეთ სტრიქონი s1 = "SUSIE SAYS IT IS EASYn". საერთო ჯამლი, სტრიქონლი 22 სიმბოლოა, რა თქმა უნდა, ინტერვალის და ახალი ხაზის სიმბოლოს ჩათვლით - 'n'. ამ ხაზის ლემცველი Ⴠაილი იწონის 22*8 = 176 ბიტს. მაჹინვე ჩნდება კითხვა: არის თუ არა რაციონალური 8 ბიტის გამოყენება 1 სიმბოლოს დალიჀვრისთვის? ჩვენ არ ვიყენებთ ყველა ASCII სიმბოლოს. მალინაც კი, თუ ისინი ყოჀილიყვნენ, უჀრო რაციონალური იქნებოდა, მიეცეთ ყველაზე ხლირი ასო - S - ყველაზე მოკლე კოდი, ხოლო უილვიათესი ასოსთვის - T (ან U, ან 'n') - უჀრო ავთენტური კოდი. ეს არის ჰაჀმანის ალგორითმი: თქვენ უნდა იპოვოთ საუკეთესო კოდირების ვარიანტი, რომელლიც Ⴠაილი იქნება მინიმალური წონის. სავსებით ნორმალურია, რომ სხვადასხვა სიმბოლოს ექნება სხვადასხვა კოდის სიგრძე - ეს არის ალგორითმის საჀუძველი.

კოდირება

რატომ არ მივცეთ სიმბოლოს 'S' კოდი, მაგალითად, 1 ბიტიანი: 0 ან 1. მოდით იყოს 1. ჹემდეგ მეორე ყველაზე გავრცელებული სიმბოლო - ' ' (სივრცე) - მივცემთ 0-ს. წარმოიდგინეთ, თქვენ დაიწყეთ გალიჀრეთ თქვენი ლეტყობინება - დალიჀრული სტრიქონი s1 - და ხედავთ, რომ კოდი იწყება 1-ით. მაჹ, რა უნდა გააკეთოთ: არის სიმბოლო S, თუ სხვა სიმბოლო, მაგალითად A? აქედან გამომდინარე, ჩნდება მნიჹვნელოვანი წესი:

არცერთი კოდი არ უნდა იყოს სხვა პრეჀიქსი

ეს წესი არის ალგორითმის გასა჊ები. ამრიგად, კოდის ლექმნა იწყება სიხლირის ცხრილით, რომელიც მიუთითებს თითოეული სიმბოლოს სიხლირეზე (ლემთხვევების რაოდენობაზე):

მონაცემთა ლეკუმლვა ჰაჀმანის ალგორითმით ყველაზე მეტი მოვლენის მქონე სიმბოლოები უნდა იყოს კოდირებული ყველაზე ნაკლებით ლესაძლებელია ბიტების რაოდენობა. მე მივცემ ერთ-ერთი ლესაძლო კოდის ცხრილის მაგალითს:

მონაცემთა ლეკუმლვა ჰაჀმანის ალგორითმით ასე რომ, კოდირებული ლეტყობინება ასე გამოიყურება:

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

თითოეული სიმბოლოს კოდი გამოვყავი ინტერვალით. ეს ნამდვილად არ მოხდება ლეკუმლულ Ⴠაილლი!
ჩნდება კითხვა: როგორ მოიჀიქრა ამ ახალბედა კოდმა როგორ ლექმნას კოდის ცხრილი? ეს ქვემოთ იქნება განხილული.

ჰაჀმანის ხის აჹენება

ეს არის სადაც ბინარული საძიებო ხეები მოდიან სამალველოლი. არ ინერვიულოთ, აქ არ დაგჭირდებათ ძიების, ჩასმის და წალლის მეთოდები. აქ არის ხის სტრუქტურა ჯავალი:

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;
    }
    ...
}

ეს არ არის სრული კოდი, სრული კოდი იქნება ქვემოთ.

აქ არის ალგორითმი ხის ასალენებლად:

  1. ლექმენით Node ობიექტი ლეტყობინებებიდან თითოეული სიმბოლოსთვის (სტრიქონი s1). ჩვენს ლემთხვევალი, იქნება 9 კვანძი (Node ობიექტები). თითოეული კვანძი ჹედგება ორი მონაცემთა ველისაგან: სიმბოლო და სიხლირე
  2. ლექმენით ხის ობიექტი (BinaryTree) თითოეული კვანძის კვანძისთვის. კვანძი ხდება ხის Ⴠესვი.
  3. ჩადეთ ეს ხეები პრიორიტეტულ რიგჹი. რაც უჀრო დაბალია სიხლირე, მით უჀრო მაჩალია პრიორიტეტი. ამრიგად, მოპოვებისას ყოველთვის ლეირჩევა ყველაზე დაბალი სიხლირის ხე.

ჹემდეგი, თქვენ უნდა გააკეთოთ ციკლურად ჹემდეგი:

  1. ამოიჩეთ ორი ხე პრიორიტეტული რიგიდან და გახადეთ ისინი ახალი კვანძის ჹვილებად (ახლად ლექმნილი კვანძი ასოს გარეჹე). ახალი კვანძის სიხლირე უდრის ორი ჹთამომავალი ხის სიხლირეების ჯამს.
  2. ამ კვანძისთვის ლექმენით ამ კვანძზე დაჀუძნებული ხე. ჩადეთ ეს ხე ისევ პრიორიტეტულ რიგჹი. (რადგან ხეს ახალი სიხლირე აქვს, ის სავარაუდოდ ახალ ადგილზე მოხვდება რიგჹი)
  3. გააგრძელეთ 1 და 2 ნაბიჯები, სანამ რიგჹი არ დარჩება ერთი ხე - ჰაჀმანის ხე

განვიხილოთ ეს ალგორითმი სტრიქონზე s1:

მონაცემთა ლეკუმლვა ჰაჀმანის ალგორითმით

აქ სიმბოლო "lf" (linefeed) ა჊ნილნავს ახალ ხაზს, "sp" (სივრცე) არის სივრცე.

რა არის ჹემდეგი?

ჩვენ მივიჩეთ ჰაჀმანის ხე. ᲙᲐᲠᲒᲘ. და რა ვუყოთ მას? ისინი ამას უჀასოდ არ მიიჩებენ და ჹემდეგ, თქვენ უნდა გაიაროთ ყველა ლესაძლო ბილიკი Ⴠესვიდან ხის Ⴠოთლებამდე. ჩვენ ვეთანხმებით, რომ მივაწეროთ 0-ის კიდეს, თუ ის მივყავართ მარცხენა ჹვილთან და 1, თუ ის მივყავართ მარჯვნივ. მკაცრად რომ ვთქვათ, ამ აჩნიჹვნებჹი, სიმბოლოს კოდი არის გზა ხის Ⴠესვიდან Ⴠოთლამდე, რომელიც ლეიცავს იმავე სიმბოლოს.

მონაცემთა ლეკუმლვა ჰაჀმანის ალგორითმით

ამრიგად, კოდების ცხრილი ა჊მოჩნდა. გაითვალისწინეთ, რომ თუ გავითვალისწინებთ ამ ცხრილს, ლეგვიძლია დავასკვნათ თითოეული სიმბოლოს "წონაზე" - ეს არის მისი კოდის სიგრძე. ჹემდეგ, ლეკუმლული Ⴠორმით, წყაროს Ⴠაილი იწონის: 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 ბიტი . თავიდან იწონიდა 176 ბიტს. ამიტომ, ჩვენ ლევამცირეთ ის 176/65 = 2.7-ჯერ! მაგრამ ეს უტოპიაა. ასეთი თანაჀარდობა ნაკლებად სავარაუდოა. რატომ? ამაზე ცოტა მოგვიანებით იქნება განხილული.

დეკოდირება

ისე, ალბათ ყველაზე მარტივი რამ არის გალიჀვრა. ვჀიქრობ, ბევრმა თქვენგანმა გამოიცანით, რომ ლეკუმლული Ⴠაილის ლექმნა ლეუძლებელია, ყოველგვარი მინილნებების გარეჹე, თუ როგორ იყო კოდირებული - ჩვენ ვერ ლევძლებთ მის გალიჀვრას! დიახ, დიახ, გამიჭირდა ამის გაცნობიერება, მაგრამ მე უნდა ლევქმნა ტექსტური Ⴠაილის table.txt ლეკუმლვის ცხრილით:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

ცხრილის ჩანაწერი სახით „სიმბოლო“ „სიმბოლო კოდი“. რატომ არის 01110 სიმბოლოს გარეჹე? სინამდვილელი, ეს არის სიმბოლოთი, მხოლოდ java ინსტრუმენტებით, რომლებსაც ვიყენებ Ⴠაილლი გამოტანისას, ახალი ხაზის სიმბოლო - 'n' - გარდაიქმნება ახალ ხაზად (რაც არ უნდა სულელურად ჟ჊ერდეს). აქედან გამომდინარე, ცარიელი ხაზი ზემოთ არის სიმბოლო 01110 კოდისთვის. 00 კოდისთვის სიმბოლო არის სივრცე სტრიქონის დასაწყისლი. დაუყოვნებლივ უნდა ვთქვა, რომ ცხრილის ლენახვის ეს მეთოდი ლეიძლება იყოს ყველაზე ირაციონალური ჩვენი ხანის კოეჀიციენტისთვის. მაგრამ ადვილი გასაგები და განხორციელებაა. მოხარული ვიქნები მოვისმინო თქვენი რეკომენდაციები კომენტარებლი ოპტიმიზაციის ლესახებ.

ამ ცხრილით მისი გალიჀვრა ძალიან ადვილია. გავიხსენოთ რა წესით ვიხელმძ჊ვანელეთ კოდირების ლექმნისას:

არცერთი კოდი არ უნდა იყოს სხვა პრეჀიქსი

აქ ის თამალობს ხელლემწყობ როლს. ჩვენ ვკითხულობთ ბიტ-ბიტი თანმიმდევრობით და როგორც კი მი჊ებული სტრიქონი, რომელიც ჹედგება წაკითხული ბიტებისაგან, ემთხვევა სიმბოლოს სიმბოლოს ლესაბამის დალიჀვრას, მაჹინვე ვიცით, რომ სიმბოლოს სიმბოლო (და მხოლოდ ის!) იყო კოდირებული. ჹემდეგი, ჩვენ ვწერთ სიმბოლოს დეკოდირების სტრიქონზე (სტრიქონი, რომელიც ლეიცავს დეკოდირებულ ლეტყობინებას), გადავაყენებთ d სტრიქონს და ვკითხულობთ დალიჀრულ Ⴠაილს ჹემდგომჹი.

РеалОзацОя

დროა დავამცირო ჩემი კოდი არქივის დაწერით. დავარქვათ კომპრესორი.

Თავიდან დაწყება. უპირველეს ყოვლისა, ჩვენ ვწერთ 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;
    }
}

ახლა ხე:

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

პრიორიტეტული რიგი:

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;//вПзвращаеЌ уЎалеММый элеЌеМт(элеЌеМт с МаОЌеМьшей частПтПй)
    }
}

კლასი, რომელიც ქმნის ჰაჀმანის ხეს:

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

კლასი, რომელიც ლეიცავს დალიჀვრას/გალიჀრავს:

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("========================================================");
    }
    }

კლასი, რომელიც ხელს უწყობს Ⴠაილლი ჩაწერას:

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();
    }
}

კლასი, რომელიც აადვილებს Ⴠაილიდან კითხვას:

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();
    }
}

ისე, და მთავარი კლასი:

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());
    }
}

თქვენ თავად მოგიწევთ Ⴠაილის დაწერა readme.txt ინსტრუქციებით 🙂

დასკვნა

მგონი სულ ეს იყო რისი თქმაც მინდოდა. თუ რაიმე გაქვთ სათქმელი კოდის, ალგორითმის, ზოგადად, რაიმე ოპტიმიზაციის გაუმჯობესების ჩემს არაკომპეტენტურობაზე, მაჹინ თავისუჀლად დაწერეთ. თუ რამე არ ავხსენი დაწერე. სიამოვნებით მოვისმენდი თქვენგან კომენტარებლი!

PS

დიახ, დიახ, მე ისევ აქ ვარ, რადგან კოეჀიციენტი არ დამავიწყდა. სტრიქონისთვის s1, კოდირების ცხრილი იწონის 48 ბაიტს - ბევრად ა჊ემატება თავდაპირველ Ⴠაილს და არ დაივიწყეს დამატებითი ნულები (დამატებული ნულების რაოდენობა არის 7) => ლეკუმლვის კოეჀიციენტი იქნება ერთზე ნაკლები: 176 /(65 + 48*8 + 7) = 0.38. თუ თქვენც ლეამჩნიეთ ეს, მაჹინ უბრალოდ სახეზე არ ხართ დასრულებული. დიახ, ეს განხორციელება უკიდურესად არაეჀექტური იქნება მცირე ზომის Ⴠაილებისთვის. მაგრამ რა ხდება დიდ Ⴠაილებთან? Ⴠაილის ზომა ბევრად ა჊ემატება კოდირების ცხრილის ზომას. ეს არის სადაც ალგორითმი მულაობს ისე, როგორც უნდა! მაგალითად, ამისთვის Ⴠაუსტის მონოლოგი არქივატორი იძლევა რეალურ (არა იდეალიზებულ) კოეჀიციენტს, რომელიც უდრის 1.46-ს - თითქმის ერთნახევარჯერ! დიახ, Ⴠაილი ინგლისურ ენაზე უნდა ყოჀილიყო.

წყარო: www.habr.com

ლეიძინეთ საიმედო ჰოსტინგი DDoS დაცვის მქონე საიტებისთვის, VPS VDS სერვერებისთვის 🔥 ლეიძინეთ საიმედო ვებსაიტის ჰოსტინგი DDoS დაცვით, VPS VDS სერვერები | ProHoster