package com.atguigu.huffmancode;

import com.sun.org.glassfish.external.statistics.CountStatistic;
import com.sun.org.glassfish.external.statistics.StringStatistic;

import java.util.*;

/**
 * @创建人 wdl
 * @创建时间 2021/3/27
 * @描述
 */
public class HuffmanCode {
    public static void main(String[] args) {
        String content="i like like like java do you like a java";
        byte[] contentBytes = content.getBytes();
        System.out.println(contentBytes.length);//40

        List<Node> nodes = getNodes(contentBytes);
        System.out.println(nodes);

        //测试一把,创建的二叉树
        System.out.println("赫夫曼树");
        Node huffmanTreeRoot = createHuffmanTree(nodes);
        System.out.println("前序遍历");
        huffmanTreeRoot.preOrder();

        //测试一把是否生成了对应的赫夫曼编码
        getCodes(huffmanTreeRoot,"",stringBuilder);
        System.out.println("生成的赫夫曼编码表"+huffmanCodes);


    }

    //生成赫夫曼树对应的赫夫曼编码
    //思路:
    //1.将赫夫曼编码表存放在Map<Byte,String>形式
    static Map<Byte,String> huffmanCodes= new HashMap<Byte,String>();
    // 32->01 97->100...
    //2.在生成赫夫曼编码表示,需要去拼接璐姐,定义一个StringBuilder存储某个叶子结点的路径
    static  StringBuilder stringBuilder=new StringBuilder();

    /**
     * 功能:将传入的node节点的所有叶子节点的赫夫曼编码的到,并放入到huffmanCodes集合
     * @param node  传入节点
     * @param code 路径:左子节点是0,右子节点是1
     * @param stringBuilder 是用于拼接路径
     */
    private static void getCodes(Node node,String code,StringBuilder stringBuilder){
        StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
        //将code加入到stringBuild2
        stringBuilder2.append(code);
        if(node!=null){
            //如果node==null不处理
            //判断当前node是叶子结点还是非叶子节点
            if(node.data==null){//非叶子节点
                //递归处理
                //向左
                getCodes(node.left,"0",stringBuilder2);
                //向右
                getCodes(node.right,"1",stringBuilder2);

            }else {//说明是一个叶子结点
                //表示找到了某个叶子节点的最后
                huffmanCodes.put(node.data,stringBuilder2.toString());
            }
        }

    }








    //前序遍历的方法
    private static void preOrder(Node root){
        if(root!=null){
            root.preOrder();
        }else {
            System.out.println("赫夫曼树为空");
        }
    }




    /**
     *
     * @param bytes 接收字节数组
     * @return 返回的就是List形式
     */
    private static List<Node> getNodes(byte[] bytes){
        //1.创建一个ArrayList
        ArrayList<Node> nodes = new ArrayList<>();

        //遍历bytes 统计每一个byte出现的次数->map[key,value]
        HashMap<Byte, Integer> counts = new HashMap<>();
        for(byte b:bytes){
            Integer count=counts.get(b);
            if (count==null){//map中还没有这个字符数据,第一次
                counts.put(b,1);
            }else {
                counts.put(b,count+1);
            }
        }
        //把每一个键值对转成Node对象,并加入到nodes集合
        //遍历map
        for(Map.Entry<Byte,Integer> entry:counts.entrySet()){
            nodes.add(new Node(entry.getKey(),entry.getValue()));
        }
        return nodes;
    }

    //可以通过List创建对应的赫夫曼树
    private static Node createHuffmanTree(List<Node> nodes){
        while (nodes.size()>1){
            //排序,从小到大
            Collections.sort(nodes);
            //取出第一颗最小的二叉树
            Node leftNode = nodes.get(0);
            //取出第二颗最小的二叉树
            Node rightNode = nodes.get(1);
            //创建一颗新的二叉树,它的根节点没有data,只有权值
            Node parent=new Node(null,leftNode.weight+rightNode.weight);
            parent.left=leftNode;
            parent.right=rightNode;

            //将已经处理的两颗二叉树从nodes删除
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            //将新的二叉树,加入到nodes
            nodes.add(parent);

        }
        //nodes最后的节点,就是哈夫曼树的根节点
        return nodes.get(0);

    }



}

//创建Node,待数据和权值
class Node implements Comparable<Node>{
    Byte data;//存放数据(字符)本身,比如'a'=>97 ' '=>32
    int weight;//权值,表示字符出现的次数
    Node left;
    Node right;
    public Node(Byte data, int weight) {
        this.data = data;
        this.weight = weight;
    }

    @Override
    public int compareTo(Node o) {
        //从小到大排序
        return this.weight-o.weight;
    }

    @Override
    public String toString() {
        return "Node{" +
                "data=" + data +
                ", weight=" + weight +
                '}';
    }

    //前序遍历
    public void preOrder(){
        System.out.println(this);
        if(this.left!=null){
            this.left.preOrder();
        }
        if (this.right!=null){
            this.right.preOrder();
        }
    }


}