Data Type Java

/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * Various number-related routines and classes that are frequently used.
 * 
 * @author barclay
 */
public class Number {
  /**
   * Default number of decimal places to round to:
   */
  public static final int DECIMAL_PLACES = 2;
  /**
   * Round a float to the default number of decimal places.
   * 
   * @param value
   *          The value to round.
   * @return The rounded value as a float.
   * @see #DECIMAL_PLACES
   */
  public static float Round(float value) {
    return Round(value, DECIMAL_PLACES);
  }
  /**
   * Round a float to the specified number of decimal places.
   * 
   * @param value
   *          The value to round.
   * @param places
   *          The number of decimal points.
   * @return The rounded value as a float.
   */
  public static float Round(float value, int places) {
    float p = (float) Math.pow(10, places);
    value = value * p;
    float tmp = Math.round(value);
    return (float) tmp / p;
  }
  /**
   * Clamp a value to min or max,
   * inclusive.
   * 
   * @param value
   *          The value to clamp.
   * @param min
   *          The minimum value.
   * @param max
   *          The maximum value.
   * @return If value is greater than max then
   *         max, else if value is less than
   *         min then min, else value.
   */
  public static float Clamp(float value, float min, float max) {
    if (value > max)
      return max;
    if (value < min)
      return min;
    return value;
  }
  public enum TrendState {
    DOWN_15_GOOD,
    DOWN_15_BAD,
    DOWN_30_GOOD,
    DOWN_30_BAD,
    DOWN_45_GOOD,
    DOWN_45_BAD,
    UP_15_GOOD,
    UP_15_BAD,
    UP_30_GOOD,
    UP_30_BAD,
    UP_45_GOOD,
    UP_45_BAD,
    DOWN_15,
    UP_15,
    FLAT,
    FLAT_GOAL,
    UNKNOWN
  };
  public static TrendState getTrendState(float oldTrend, float newTrend,
      float goal, float sensitivity, float stdDev) {
    sensitivity = sensitivity * stdDev;
    float half = sensitivity / 2.0f;
    float quarter = sensitivity / 4.0f;
    if (oldTrend == newTrend) {
      // truly flat trend
      if (newTrend == goal)
        // perfect!
        return TrendState.FLAT_GOAL;
      else if (newTrend < goal && newTrend + quarter > goal)
        // flat near the goal!
        return TrendState.FLAT_GOAL;
      else if (newTrend > goal && newTrend - quarter < goal)
        // flat near the goal!
        return TrendState.FLAT_GOAL;
      else
        return TrendState.FLAT;
    } else if (oldTrend > newTrend) {
      // going down
      if (oldTrend > goal && newTrend > goal) {
        // toward goal
        if (oldTrend - newTrend > sensitivity)
          // huge drop
          return TrendState.DOWN_45_GOOD;
        else if (oldTrend - newTrend > half)
          // big drop
          return TrendState.DOWN_30_GOOD;
        else if (oldTrend - newTrend > quarter)
          // little drop
          return TrendState.DOWN_15_GOOD;
        else {
          // under bounds for flat
          if (newTrend - quarter < goal)
            // flat near the goal!
            return TrendState.FLAT_GOAL;
          else
            // flat elsewhere
            return TrendState.FLAT;
        }
      } else if (oldTrend < goal && newTrend < goal) {
        // away from goal
        if (oldTrend - newTrend > sensitivity)
          // huge drop
          return TrendState.DOWN_45_BAD;
        else if (oldTrend - newTrend > half)
          // big drop
          return TrendState.DOWN_30_BAD;
        else if (oldTrend - newTrend > quarter)
          // little drop
          return TrendState.DOWN_15_BAD;
        else {
          // under bounds for flat
          if (newTrend + quarter > goal)
            // flat near the goal!
            return TrendState.FLAT_GOAL;
          else
            // flat elsewhere
            return TrendState.FLAT;
        }
      } else
        // crossing goal line
        return TrendState.DOWN_15;
    } else if (oldTrend < newTrend) {
      // going up
      if (oldTrend < goal && newTrend < goal) {
        // toward goal
        if (newTrend - oldTrend > sensitivity)
          // big rise
          return TrendState.UP_45_GOOD;
        else if (newTrend - oldTrend > half)
          // little rise
          return TrendState.UP_30_GOOD;
        else if (newTrend - oldTrend > quarter)
          // little rise
          return TrendState.UP_15_GOOD;
        else {
          // under bounds for flat
          if (newTrend + quarter > goal)
            // flat near the goal!
            return TrendState.FLAT_GOAL;
          else
            // flat elsewhere
            return TrendState.FLAT;
        }
      } else if (oldTrend > goal && newTrend > goal) {
        // away from goal
        if (newTrend - oldTrend > sensitivity)
          // big rise
          return TrendState.UP_45_BAD;
        else if (newTrend - oldTrend > half)
          // little rise
          return TrendState.UP_30_BAD;
        else if (newTrend - oldTrend > quarter)
          // little rise
          return TrendState.UP_15_BAD;
        else {
          // under bounds for flat
          if (newTrend - quarter < goal)
            // flat near the goal!
            return TrendState.FLAT_GOAL;
          else
            // flat elsewhere
            return TrendState.FLAT;
        }
      } else {
        // crossing goal line
        return TrendState.UP_15;
      }
    } else
      // ??
      return TrendState.UNKNOWN;
  }
  /**
   * An exponentially smoothed weighted moving average. Not thread safe. Trend
   * is calculated thusly: 

   * 
   * trend[n] := trend[n-1] + smoothing_percentage * (value[n] - value[n-1])
   * 

   * 
   * @author barclay
   */
  public static class Trend {
    /**
     * Default smoothing percentage. This value will be used to scale the
     * previous entry by multiplying the previous entry's value and adding that
     * to the current value, so a smoothing percentage of 0.1 is 10%.
     */
    private static final float DEFAULT_SMOOTHING = 0.1f;
    private int mNEntries = 0;
    private float mSmoothing = DEFAULT_SMOOTHING;
    private float mSum = 0.0f;
    private boolean mFirst = true;
    private float mTrendLast = 0.0f;
    public float mTrendPrev = 0.0f;
    public float mTrend = 0.0f;
    public float mMin = 0.0f;
    public float mMax = 0.0f;
    public float mMean = 0.0f;
    /**
     * Default constructor. Set the smoothing percentage to the default.
     * 
     * @see #DEFAULT_SMOOTHING
     */
    public Trend() {
    }
    /**
     * Constructor
     * 
     * @param smoothing
     *          Sets the smoothing percentage to the specified value.
     * @see #DEFAULT_SMOOTHING
     */
    public Trend(float smoothing) {
      mSmoothing = smoothing;
    }
    /**
     * Copy Constructor
     * 
     * @param source
     *          Returns a new instance of Trend with all data set to source.
     * @see #DEFAULT_SMOOTHING
     */
    public Trend(Trend source) {
      mNEntries = source.mNEntries;
      mSmoothing = source.mSmoothing;
      mSum = source.mSum;
      mFirst = source.mFirst;
      mTrendLast = source.mTrendLast;
      mTrendPrev = source.mTrendPrev;
      mTrend = source.mTrend;
      mMin = source.mMin;
      mMax = source.mMax;
      mMean = source.mMean;
    }
    /**
     * Return the smoothing constant used by the Trend.
     * 
     * @return The smoothing constant as a float.
     */
    public float getSmoothing() {
      return mSmoothing;
    }
    /**
     * Update the trend with a new value. The value is implicitly "later" in the
     * sequence than all previous values.
     * 
     * @param val
     *          The value to add to the series.
     */
    public void update(float val) {
      mNEntries++;
      mSum += val;
      float oldMean = mMean;
      mMean += (val - oldMean) / mNEntries;
      // T(n) = T(n-1) + 0.1(V(n) - T(n-1))
      // : T(n) is the trend number for day n
      // : V(n) is the value number for day n
      // : S is the smoothing factor (default 0.1)
      if (mFirst == true) {
        mFirst = false;
        mTrend = val;
        mMin = val;
        mMax = val;
      } else {
        mTrend = mTrendLast + (mSmoothing * (val - mTrendLast));
        if (mTrend < mMin)
          mMin = mTrend;
        if (mTrend > mMax)
          mMax = mTrend;
      }
      mTrendPrev = mTrendLast;
      mTrendLast = mTrend;
    }
  }
  /**
   * Class for keeping track of various statistics intended to be updated
   * incrementally, including total number of updates, sum, mean, variance, and
   * standard deviation of the series. Not thread safe.
   * 
   * @author barclay
   */
  public static class RunningStats {
    public int mNDatapoints = 0;
    public int mNEntries = 0;
    public float mSum = 0.0f;
    public float mMean = 0.0f;
    public float mEntryMean = 0.0f;
    public float mVarSum = 0.0f;
    public float mVar = 0.0f;
    public float mStdDev = 0.0f;
    /**
     * Sole constructor. Initializes all stats to 0.
     */
    public RunningStats() {
    }
    /**
     * Copy Constructor
     * 
     * @param source
     *          Returns a new instance of RunningStats with all data set to
     *          source.
     */
    public RunningStats(RunningStats source) {
      mNDatapoints = source.mNDatapoints;
      mNEntries = source.mNEntries;
      mSum = source.mSum;
      mMean = source.mMean;
      mEntryMean = source.mEntryMean;
      mVarSum = source.mVarSum;
      mVar = source.mVar;
      mStdDev = source.mStdDev;
    }
    /**
     * update the statistics with the specified value. The value may be an
     * aggregate of several other values, as indicated by the second parameter.
     * A separate value will be recored for per-entry and and per-update means.
     * 
     * @param val
     *          The value to update the statistics with.
     * @param nEntries
     *          The number of entries this value is an aggregate of.
     */
    public void update(float val, int nEntries) {
      mNDatapoints++;
      mNEntries += nEntries;
      mSum += val;
      // Mean is calculated thusly to avoid float expansion and contraction,
      // which
      // would minimize accuracy.
      float oldMean = mMean;
      mMean += (val - oldMean) / mNDatapoints;
      mVarSum += (val - oldMean) * (val - mMean);
      mVar = mVarSum / mNDatapoints;
      mStdDev = (float) Math.sqrt(mVar);
      if (mNEntries > 0) {
        float oldEntryMean = mEntryMean;
        mEntryMean += (val - oldEntryMean) / mNEntries;
      }
      return;
    }
  }
  /**
   * Class for keeping track of the Standard Deviation over the last X values.
   * 
   * @author barclay
   */
  public static class WindowedStdDev {
    private Lock mLock;
    private ArrayList mValues;
    private int mHistory;
    /**
     * Sole constructor. Initializes all stats to 0.
     */
    public WindowedStdDev(int history) {
      mHistory = history;
      mValues = new ArrayList(mHistory);
      mLock = new ReentrantLock();
    }
    /**
     * Copy Constructor
     * 
     * @param source
     *          Returns a new instance of WindowedStdDev with all data set to
     *          source.
     */
    public WindowedStdDev(WindowedStdDev source) {
      mLock = new ReentrantLock();
      source.waitForLock();
      mHistory = source.mHistory;
      mValues = new ArrayList(mHistory);
      for (int i = 0; i < source.mValues.size(); i++) {
        try {
          mValues.add(new Float(source.mValues.get(i)));          
        } catch(IndexOutOfBoundsException e) {
          break;
        }
      }
      source.unlock();
    }
    public void waitForLock() {
      while (lock() == false) {
      }
    }
    public boolean lock() {
      try {
        return mLock.tryLock(250L, TimeUnit.MILLISECONDS);
      } catch (InterruptedException e) {
        return false;
      }
    }
    public void unlock() {
      mLock.unlock();
    }
    /**
     * update the std dev with the specified value.
     * 
     * @param val
     *          The value to update the statistics with.
     */
    public void update(float val) {
      waitForLock();
      mValues.add(new Float(val));
      if (mValues.size() > mHistory) {
        try {
          mValues.remove(0);
        } catch(IndexOutOfBoundsException e) {
          // nothing
        }
      }
      unlock();
      return;
    }
    /**
     * Fetch current Standard Deviation.
     * 
     * @param val
     *          The value to update the statistics with.
     */
    public float getStandardDev() {
      float mean = 0.0f;
      float meanSqr = 0.0f;
      float variance = 0.0f;
      float delta = 0.0f;
      float val = 0.0f;
      waitForLock();
      int nValues = mValues.size();
      for (int i = 0; i < nValues; i++) {
        try {
          val = mValues.get(i);
          delta = val - mean;
          mean = mean + delta / (i + 1);
          meanSqr = meanSqr + delta * (val - mean);
        } catch(IndexOutOfBoundsException e) {
          break;
        }
      }
      unlock();
      
      variance = meanSqr / nValues;
      return (float) Math.sqrt(variance);
    }
  }
  /**
   * Performs a standard Pearson linear correlation on multiple series of data
   * at one, returning the results in a matrix.
   * 
   * @author barclay
   */
  public static class LinearMatrixCorrelation {
    private int mNumSeries = 0;
    private int mNEntries = 0;
    private Float[] mSum;
    private Float[] mMean;
    private Float[] mSumSquare;
    private Float[] mStdDev;
    private Float[][] mSumCoproduct;
    private Float[][] mCovariance;
    private Float[][] mCorrelation;
    /**
     * Constructor. Allocates internal data structures and zero's out the output
     * matrix data.
     * 
     * @param numSeries
     *          The number of series that will be included in each call to
     *          update().
     */
    public LinearMatrixCorrelation(int numSeries) {
      mNumSeries = numSeries;
      mSum = new Float[numSeries];
      mMean = new Float[numSeries];
      mSumSquare = new Float[numSeries];
      mStdDev = new Float[numSeries];
      mSumCoproduct = new Float[numSeries][];
      mCovariance = new Float[numSeries][];
      mCorrelation = new Float[numSeries][];
      for (int i = 0; i < numSeries; i++) {
        mSum[i] = 0.0f;
        mMean[i] = 0.0f;
        mSumSquare[i] = 0.0f;
        mStdDev[i] = 0.0f;
        mSumCoproduct[i] = new Float[numSeries];
        mCovariance[i] = new Float[numSeries];
        mCorrelation[i] = new Float[numSeries];
        for (int j = 0; j < numSeries; j++) {
          mSumCoproduct[i][j] = 0.0f;
          mCovariance[i][j] = 0.0f;
          mCorrelation[i][j] = 0.0f;
        }
      }
    }
    /**
     * Adds a vector (array) of values, 1 per series, to the calculations.
     * Graphically, all values are considered to be the at the same X (or Y)
     * position, and the values in the array argument denote the corresponding Y
     * (or X) value specific to the each series. Thus, the parameter x is an
     * array of values x[0 .. numSeries-1], one value per series, all of which
     * occured at the same "time." If a series has no such value, the entry in
     * the array should be null, as the length of the array must match the
     * numSeries past into the constructor at each invocation.
     * 
     * @param x
     *          The values for each series as Floats.
     * @return true if the input acceptable, else false if the length of
     *         x[] does not match numSeries or a value
     *         less than 1 was passed to the constructor.
     * @see LinearMatrixCorrelation#LinearMatrixCorrelation
     */
    public boolean update(Float[] x) {
      float oldMean;
      if (x.length != mNumSeries)
        return false;
      if (mNEntries + 1 > mNumSeries)
        return false;
      mNEntries++;
      float sweep = (mNEntries - 1.0f) / mNEntries;
      for (int i = 0; i < x.length; i++) {
        if (x[i] != null) {
          mSum[i] += x[i];
          oldMean = mMean[i];
          mMean[i] += (x[i] - oldMean) / mNEntries;
          mSumSquare[i] += (x[i] - oldMean) * (x[i] - mMean[i]);
          mStdDev[i] = (float) Math.sqrt(mSumSquare[i] * sweep);
        }
      }
      for (int i = 0; i < x.length; i++) {
        if (x[i] != null) {
          for (int j = i + 1; j < x.length; j++) {
            if (x[j] != null) {
              mSumCoproduct[i][j] += (x[i] - mMean[i]) * (x[j] - mMean[j]);
              mCovariance[i][j] = mSumCoproduct[i][j] * sweep;
              mCorrelation[i][j] = mCovariance[i][j]
                  / (mStdDev[i] * mStdDev[j]);
              mCorrelation[j][i] = mCovariance[i][j]
                  / (mStdDev[i] * mStdDev[j]);
              mCorrelation[i][j] = mCovariance[i][j]
                  / (mStdDev[i] * mStdDev[j]);
            }
          }
        }
        mCorrelation[i][i] = 1.0f;
      }
      return true;
    }
    /**
     * Returns a reference to the correlation output matrix. The upper right
     * triangle is a mirror of the lower left, and the dividing diagonal the
     * identity correlation, i.e., 1.0f. In order to run through the matrix
     * without duplicates (e.g., processing both output[i][j] and output[j][i],
     * use a construct like: 

     * 
     * 

     * for (int i = 0; i < output.length; i++) {
     *   for (int j = i+1; j < output.length; j++) {
     *     if (output[i][j] != null) { ... }
     *   }
     * }
     * 

     * 
     * Note that any correlations that could not be calculated, either due to
     * lack of datapoints or structure of the data, will be null.
     * 
     * @return Float[][], the output correlation matrix.
     */
    public Float[][] getCorrelations() {
      return mCorrelation;
    }
    /**
     * Return a string interpretation of the linear correlation value. Note that
     * this is highly dependent on the data being correlated, and should be no
     * means be taken as gospel.
     * 
     * @param c
     *          The correlation value, should be -1.0f <= c <= 1.0f.
     * @return A string interpretation.
     */
    public static String correlationToString(float c) {
      if (c > 0.5)
        return "Strong";
      if (c < -0.5)
        return "Inverse Strong";
      if (c > 0.3)
        return "Medium";
      if (c < -0.3)
        return "Inverse Medium";
      return "Weak";
    }
  }
}