package yi.stat;

import java.util.Random;

public class StatUtil {
  /**
   * Normalize the data in the given array
   *
   * For example, data {200, 400, 800, 400} will be Normalized as
   * {63, 127, 255, 127} if minimum = 1, maximum = 801 and unit = 255.  Note the
   * data should fall into the range [minimum, maximum].  If a data is less than
   * minimum, it will be normalized as 0; if it is greater than the maximum, it
   * will be normalized as the unit.  The normalized data will in the range of
   * [0, unit]
   * @param data the data to be normalized
   * @param minimum the minimum value of the data (inclusive)
   * @param maximum the maximum value of the data (inclusive)
   * @param unit the unit of the normalized data
   * @return the normalized data
   */
  public static int[] normalize(int[] data, int minimum, int maximum, int unit) {
    int[] normalized = new int[data.length];
    for (int i=0; i<data.length; i++) {
      normalized[i] = normalize(data[i], minimum, maximum, unit);
    }
    return normalized;
  }

  public static double[] normalize(double[] data, double minimum, double maximum, double unit) {
    double[] normalized = new double[data.length];
    for (int i=0; i<data.length; i++) {
      normalized[i] = normalize(data[i], minimum, maximum, unit);
    }
    return normalized;
  }

  public static int normalize(int data, int minimum, int maximum, int unit) {
    int normalized;
    int data_range = maximum - minimum;

    if (data < minimum) normalized = 0;
    else if (data > maximum) normalized = unit;
    else if (data_range > 0) {
      normalized = (data - minimum) * unit / data_range;
    } else {
      normalized = 0;
    }

    return normalized;
  }

  /**
   * If the minimum == maximum, the data will be always normalized to 0.
   * data <= minimum -> 0; data >= maximum -> unit
   * @param data
   * @param minimum
   * @param maximum
   * @param unit
   * @return
   */
  public static double normalize(double data, double minimum, double maximum, double unit) {
    double normalized;
    double data_range = maximum - minimum;

    if (data < minimum) normalized = 0;
    else if (data > maximum) normalized = unit;
    else if (data_range > 0) {
      normalized = (data - minimum) * unit / data_range;
    } else {
      normalized = 0;
    }

    return normalized;
  }

  /**
   * Normalize the data in the given integer array.  The minimum will be
   * normalized to 0 and the maximum will be normalized to unit
   * @param data
   * @param unit
   * @return
   */
  public static int[] normalize(int[] data, int unit) {
    int minimum  = minimum(data), maximum = maximum(data);
    return normalize(data, minimum, maximum, unit);
  }

  public static double[] normalize(double[] data, double unit) {
    double minimum  = minimum(data), maximum = maximum(data);
    return normalize(data, minimum, maximum, unit);
  }

  /**
   * Normalize the data in the given 2D int array.  See
   * #normalize(int[] data, int minimum, int maximum, int unit) for more details
   * @param data the data to be normalized
   * @param minimum the minimum value of the data (inclusive)
   * @param maximum the maximum value of the data (inclusive)
   * @param unit the unit of the normalized data
   * @return the normalized data
   */
  public static int[][] normalize(int[][] data, int minimum, int maximum, int unit) {
    int[][] normalized = new int[data.length][];
    for (int i=0; i<data.length; i++) {
      normalized[i] = normalize(data[i], minimum, maximum, unit);
    }
    return normalized;
  }

  public static double[][] normalize(double[][] data, double minimum, double maximum, double unit) {
    double[][] normalized = new double[data.length][];
    for (int i=0; i<data.length; i++) {
      normalized[i] = normalize(data[i], minimum, maximum, unit);
    }
    return normalized;
  }

  /**
   * Normalize the data in the given 2D int array.  The minimum will be
   * normalized to 0 and the maximum will be normalized to unit
   * @param data
   * @param unit
   * @return
   */
  public static int[][] normalize(int[][] data, int unit) {
    int minimum  = minimum(data), maximum = maximum(data);
    return normalize(data, minimum, maximum, unit);
  }

  /**
   * Return the maximum value in an int array.  The caller has the responsbility
   * to make sure the array has at least one element.
   * @param data the integer array
   * @return the maximum
   */
  public static int maximum(int[] data) {
    int max = data[0];
    for (int i=1; i<data.length; i++) {
      if (max < data[i]) max = data[i];
    }
    return max;
  }

  /**
   * Return the maximum value of a 2D int array.  The caller has the responsbility
   * to make sure the array has at least one element.
   * @param data the integer array
   * @return the maximum
   */
  public static int maximum(int[][] data) {
    int[] maxs = new int[data.length];
    for (int i=0; i<data.length; i++) maxs[i] = maximum(data[i]);
    return maximum(maxs);
  }

  /**
   * Return the minimum value in an int array.  The caller has the responsbility
   * to make sure the array has at least one element.
   * @param data the integer array
   * @return the minimum
   */
  public static int minimum(int[] data) {
    int min = data[0];
    for (int i=1; i<data.length; i++) {
      if (min > data[i]) min = data[i];
    }
    return min;
  }


  /**
   * Return the minimum value in an 2D int array.  The caller has the responsbility
   * to make sure the array has at least one element.
   * @param data the integer array
   * @return the minimum
   */
  public static int minimum(int[][] data) {
    int[] mins = new int[data.length];
    for (int i=0; i<data.length; i++) mins[i] = minimum(data[i]);
    return minimum(mins);
  }



  /**
   * Return the summary of the values in an int array.
   * @param data the integer array
   * @return the summary
   */
  public static long sum(int[] data) {
    long sum = 0;
    for (int i=0; i<data.length; i++) sum += data[i];
    return sum;
  }

  /**
   * Return the mean of the values in an int array.
   * @param data the integer array
   * @return the mean
   */
  public static double mean(int[] data) {
    return sum(data)/(double)data.length;
  }

  /**
   * Return the mean of the values in an double array.
   * @param data the integer array
   * @return the mean
   */
  public static double mean(double[] data) {
    return sum(data)/(double)data.length;
  }

  /**
   * Return the median of the values in an double array.
   * @param data the integer array
   * @return the mean
   */
  public static double median(double[] data) {
    double[] temp = (double[])data.clone();
    java.util.Arrays.sort(data);
    return data[data.length/2];
  }

  public static double[] medianOnCol(double[][] data) {
    if (data == null || data[0] == null || data[0].length < 1) return null;
    int d = data[0].length;
    double[] medians = new double[d];
    double[] column = new double[data.length];
    for (int j=0; j<d; j++) {
      for (int i=0; i<data.length; i++) {
        column[i] = data[i][j];
      }
      medians[j] = median(column);
    }
    return medians;
  }


  public static double[] meanOnCol(double[][] data) {
    int d = data[0].length;
    double[] means = new double[d];
    for (int i=0; i<data.length; i++) {
      for (int j=0; j<d; j++) {
        means[j] += data[i][j];
      }
    }
    for (int j=0; j<d; j++) {
      means[j] /= data.length;
    }
    return means;
  }


  /**
   * Return the variance of a data sequence.  sum((x-mean)^2)/(n-1)
   * @param data
   * @return
   */
  public static double variance(double[] data) {
    if (data.length <= 1) return 0;

    double mean = mean(data);
    double sum = 0;
    for (int i=0; i<data.length; i++) {
      double dev = data[i] - mean;
      sum += dev * dev;
    }
    return sum/(data.length-1);
  }

  /**
   * Return the variance on each column of a 2D double array.  The caller has
   * the responsbility to make sure the array has at least one element.
   * @param data the double array
   * @return the variances
   */
  public static double[] varianceOnCol(double[][] data) {
    if (data == null || data.length <= 0 || data[0] == null) return null;

    int n = data.length;
    int dim = data[0].length;

    double[] var= new double[dim];
    double[] d = new double[n];
    for (int i=0; i<dim; i++) {
      for (int j=0; j<n; j++) {
        d[j] = data[j][i];
      }
      var[i] = variance(d);
    }
    return var;
  }

  /**
   * Return the summary of the values in an 2D int array.
   * @param data the integer array
   * @return the summary
   */
  public static long sum(int[][] data) {
    long sum = 0;
    for (int i=0; i<data.length; i++) sum += sum(data[i]);
    return sum;
  }

  /**
   * Return the summary of the values in an int array.
   * @param data the integer array
   * @return the summary
   */
  public static long absoluteSum(int[] data) {
    long sum = 0;
    for (int i=0; i<data.length; i++) sum += Math.abs(data[i]);
    return sum;
  }

  /**
   * Return the summary of the values in an 2D int array.
   * @param data the integer array
   * @return the summary
   */
  public static long absoluteSum(int[][] data) {
    long sum = 0;
    for (int i=0; i<data.length; i++) sum += absoluteSum(data[i]);
    return sum;
  }




  /**
   * Return the summary of part of the values in an int array.  User is
   * responsible for checking the parameter from and to are within the range
   * @param data the integer array
   * @param from the beginning index (inclusive)
   * @param to the ending index (exclusive)
   * @return the summary
   */
  public static long sum(int[] data, int from, int to) {
    long sum = 0;
    for (int i=from; i<to; i++) sum += data[i];
    return sum;
  }

  /**
   * Return the mean of the given probabilistic histogram
   * @param histogram the histogram
   * @return the mean
   */
  public static double meanOfProbHist(int[] histogram) {
    long weighted_sum = 0;
    for (int i=0; i<histogram.length; i++) weighted_sum += i*histogram[i];
    long sum = sum(histogram);
    return (double)weighted_sum/sum;
  }

  /**
   * Return the variance of the given probabilistic histogram.  The mean is
   * pre-calculated.
   * @param histogram the histogram
   * @param mean the mean of the probilistic histogram.  User can call method
   *             meanOfProbHist to get the value
   * @return the variance
   */
  public static double varianceOfProbHist(int[] histogram, double mean) {
    double var = 0;
    double total = 0;
    for (int i=0; i<histogram.length; i++) {
      double diff = i - mean;
      var += diff * diff * histogram[i];
      total += histogram[i];
    }
    return var / total;
  }

  /**
   * Test method normalize
   */
  public static class TestNormalize {
    public static void main(String[] args) {
      int[] data = {-200, 1, 0, 200, 400, 800, 400, 1200};
      int[] normalized = normalize(data, 1, 800, 255);
      yi.util.io.OutputResult.print(System.out, normalized);
    }
  }

  /**
   * Test method meanOfProbHist
   */
  public static class TestMean {
    public static void main(String[] args) {
      int[] data = {5, 5, 5, 10, 0, 10};
      double mean = meanOfProbHist(data);
      System.out.println("mean = " + mean);
    }
  }

  /**
   * Return the maximum value in an double array.  The caller has the responsbility
   * to make sure the array has at least one element.
   * @param data the double array
   * @return the maximum
   */
  public static double maximum(double[] data) {
    double max = data[0];
    for (int i=1; i<data.length; i++) {
      if (max < data[i]) max = data[i];
    }
    return max;
  }


  /**
   * Return the maximum value of a 2D double array.  The caller has the responsbility
   * to make sure the array has at least one element.
   * @param data the double array
   * @return the maximum
   */
  public static double maximum(double[][] data) {
    double[] maxs = new double[data.length];
    for (int i=0; i<data.length; i++) maxs[i] = maximum(data[i]);
    return maximum(maxs);
  }

  /**
   * Return the maximum value of a 2D double array.  The caller has the responsbility
   * to make sure the array has at least one element.  Return the maximum on
   * each column
   * @param data the double array
   * @return the maximum
   */
  public static double[] maximumOnCol(double[][] data) {
    double[] max= (double[])data[0].clone();
    for (int i=1; i<data.length; i++) {
      double[] d = data[i];
      for (int j=0; j<d.length; j++) {
        if (d[j] > max[j]) {
          max[j] = d[j];
        }
      }
    }
    return max;
  }

  /**
   * Return the minimum value of a 2D double array.  The caller has the responsbility
   * to make sure the array has at least one element.  Return the minimum on
   * each column
   * @param data the double array
   * @return the minimum
   */
  public static double[] minimumOnCol(double[][] data) {
    double[] min= (double[])data[0].clone();
    for (int i=1; i<data.length; i++) {
      double[] d = data[i];
      for (int j=0; j<d.length; j++) {
        if (d[j] < min[j]) {
          min[j] = d[j];
        }
      }
    }
    return min;
  }

  /**
   * Return the minimum value in an double array.  The caller has the responsbility
   * to make sure the array has at least one element.
   * @param data the integer array
   * @return the minimum
   */
  public static double minimum(double[] data) {
    double min = data[0];
    for (int i=1; i<data.length; i++) {
      if (min > data[i]) min = data[i];
    }
    return min;
  }


  /**
   * Return the minimum value in an 2D double array.  The caller has the responsbility
   * to make sure the array has at least one element.
   * @param data the integer array
   * @return the minimum
   */
  public static double minimum(double[][] data) {
    double[] mins = new double[data.length];
    for (int i=0; i<data.length; i++) mins[i] = minimum(data[i]);
    return minimum(mins);
  }

  /**
   * Return the summary of the values in an double array.
   * @param data the integer array
   * @return the summary
   */
  public static double sum(double[] data) {
    double sum = 0;
    for (int i=0; i<data.length; i++) sum += data[i];
    return sum;
  }

  /**
   * Return the summary of the values in an 2D int array.
   * @param data the integer array
   * @return the summary
   */
  public static double sum(double[][] data) {
    double sum = 0;
    for (int i=0; i<data.length; i++) sum += sum(data[i]);
    return sum;
  }


  /**
   * Return the summary of the values in an double array.
   * @param data the integer array
   * @return the summary
   */
  public static double absoluteSum(double[] data) {
    double sum = 0;
    for (int i=0; i<data.length; i++) sum += Math.abs(data[i]);
    return sum;
  }

  /**
   * Return the summary of the values in an 2D double array.
   * @param data the integer array
   * @return the summary
   */
  public static double absoluteSum(double[][] data) {
    double sum = 0;
    for (int i=0; i<data.length; i++) sum += absoluteSum(data[i]);
    return sum;
  }

  /**
   * Return the summary of part of the values in an doublet array.  User is
   * responsible for checking the parameter from and to are within the range
   * @param data the integer array
   * @param from the beginning index (inclusive)
   * @param to the ending index (exclusive)
   * @return the summary
   */
  public static double sum(double[] data, int from, int to) {
    double sum = 0;
    for (int i=from; i<to; i++) sum += data[i];
    return sum;
  }

  /**
   * Randomize a list of integer numbers (from 0 to num-1)
   * @param num
   * @return
   */
  public static int[] randomize(int num) {
    Random ran = new Random();  // random number generator
    return randomize(ran, num);
  }

  /**
   * Randomize a list of integer numbers (from 0 to num-1)
   * @param ran
   * @param num
   * @return
   */
  public static int[] randomize(Random ran, int num) {
    // first construct a sorted list of numbers
    int[] sorted = new int[num];
    for (int i=0; i<num; i++) sorted[i] = i;

    // randomly pick up the numbers from the sorted list one by one
    int[] r = new int[num];
    for (int i=0; i<num; i++) {
      int pickup = ran.nextInt(num - i);
      r[i] = sorted[pickup];
      sorted[pickup] = sorted[num - 1 - i];
    }
    return r;
  }


  /**
   * From the given data, get the uniform distribution density.  The max value
   * and min value on each dimension are determined from the given data.
   * Suppose the data will uniformed distributed in the data space, this method
   * return the density.
   * @param data the 1st dim is the data index, the 2nd dim is the feature
   *        vector dimension
   * @return
   */
  public static double getUniformDistributionDensity(double[][] data) {

    // maximum and minimum on each dimension of the data
    double[] max_data = maximumOnCol(data);
    double[] min_data = minimumOnCol(data);

    // find the density for the uniform distribution
    double uniform_dist = 1;
    for (int i = 0; i < min_data.length; i++) {
      uniform_dist *= 1.0 / (max_data[i] - min_data[i]);
    }
    return uniform_dist;
  }

}