Java tutorial
/* * 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 <code>value</code> to <code>min</code> or <code>max</code>, * inclusive. * * @param value * The value to clamp. * @param min * The minimum value. * @param max * The maximum value. * @return If <code>value</code> is greater than <code>max</code> then * <code>max</code>, else if <code>value</code> is less than * <code>min</code> then <code>min</code>, else <code>value</code>. */ 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: <br> * <code> * trend[n] := trend[n-1] + smoothing_percentage * (value[n] - value[n-1]) * </code> * * @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<Float> mValues; private int mHistory; /** * Sole constructor. Initializes all stats to 0. */ public WindowedStdDev(int history) { mHistory = history; mValues = new ArrayList<Float>(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<Float>(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 * <code>update()</code>. */ 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 * <code>numSeries</code> 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 * <code>x[]</code> does not match <code>numSeries</code> 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: <br> * * <pre> * for (int i = 0; i < output.length; i++) { * for (int j = i+1; j < output.length; j++) { * if (output[i][j] != null) { ... } * } * } * </pre> * * 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"; } } }