package com.dayou.report.kmeans;


import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

public class KMeansHelper {

    public static List<Cluster> kMeansClustering(Map<String,BigDecimal> originData,int k) {
        //初始化数据集和初始簇
        List<Point> dataList = new ArrayList<>();
        originData.forEach((key,value)->{
            Point point = Point.builder().name(key).x(value.doubleValue()).build();
            dataList.add(point);
        });

        List<Cluster> clusterList = getInitCluster(dataList,k);
        while(true){
            for (int j = 0; j < k; j++) {
                clusterList.get(j).getSameList().clear();
            }
            for (Point point : dataList) {
                int index = getBelongCluster(point, clusterList); //获取point属于的那个簇在clusterList中的下标
                clusterList.get(index).getSameList().add(point); //把point加入到clusterList的对应簇中;
            }
            if (!calculateClusterCore(clusterList)) break;
        }
        clusterList = clusterList.stream().sorted(Comparator.comparing(Cluster::getX).reversed()).collect(Collectors.toList());
        for (int c=0 ; c<clusterList.size() ; c++){
            clusterList.get(c).getCorePoint().setName("第"+(c+1)+"级");
        }
        return clusterList;
    }

    /**
     * 计算出新的簇中心并返回簇的点集合是否有变化
     * @param clusterList
     * @return
     */
    public static boolean calculateClusterCore(List<Cluster> clusterList){
        boolean flag = false;
        //遍历簇集合中的每一项，更新其簇中心
        for (Cluster cluster : clusterList) {
            List<Point> sameList = cluster.getSameList();
            if (sameList.size()==0){
                return true;
            }
            double sumX = 0; //存放簇中点集合所有的X坐标之和
           // double sumY = 0; //存放簇中点集合所有的Y坐标之和
            for (Point point : sameList) {
                sumX += point.getX();
               // sumY += point.getY();
            }
            BigDecimal bigDecimal = BigDecimal.valueOf(sumX).divide(BigDecimal.valueOf(sameList.size()),2,RoundingMode.HALF_UP);
            //更新簇的中心
            Point clusterCore = new Point(null,bigDecimal.doubleValue());
            if (!Point.getIsSame(clusterCore, cluster.getCorePoint())) flag = true;
            cluster.setCorePoint(clusterCore);
        }
        return flag;
    }

    /**
     * 获取某个点属于哪个簇的下标
     * @param point
     * @return
     */
    public static int getBelongCluster(Point point, List<Cluster> clusterList){
        double closestDistance = 0.0; //存放point距离簇中心最近的距离
        int resultClusterIndex = 0; //存放point属于的那个簇的下标
        int index = 0;
        //遍历簇集合，计算point到簇中心的距离，找出point属于的簇
        for (Cluster cluster : clusterList) {
            double distance = Point.calculateMHDDistance(point, cluster.getCorePoint());
            if (index == 0) closestDistance = distance;
            if (distance < closestDistance){
                closestDistance = distance;
                resultClusterIndex = index;
            }
            index++;
        }
        return resultClusterIndex;
    }

    /**
     * 获取任意k个对象作为初始簇中心，将含有k个簇的集合返回
     * @return
     */
    public static List<Cluster> getInitCluster(List<Point> dataList,int k){
        List<Cluster> clusterList = new ArrayList<>();
        List<Integer> randomArray = getRandomList(dataList,k);
        //任意选取k个对象作为初始簇中心，数据集中k个对象的下标存放在randomArray中
        for (int i = 0; i < randomArray.size(); i++) {
            Point point = dataList.get(randomArray.get(i));
            Cluster cluster = new Cluster(point.getX(),point);
            clusterList.add(cluster);
        }
        return clusterList;
    }

    /**
     * 随机获取中心
     * @return
     */
    public static List<Integer> getRandomList(List<Point> dataList,int k){
        List<Integer> collect = new ArrayList<>();
        Random random = new Random();
        for (int i =0 ; i<k; i++){
            int r = random.nextInt(dataList.size());
            while (collect.contains(r)){
                r = random.nextInt(dataList.size());
            }
            collect.add(r);
        }
        return collect;
    }

}


