org.opentripplanner.common.Histogram Maven / Gradle / Ivy
package org.opentripplanner.common;
import com.google.common.base.Strings;
import gnu.trove.iterator.TIntIntIterator;
import gnu.trove.map.TIntIntMap;
import gnu.trove.map.hash.TIntIntHashMap;
import org.apache.commons.math3.random.MersenneTwister;
import java.util.stream.IntStream;
/**
* For design and debugging purposes, a simple class that tracks the frequency of different numbers.
*/
public class Histogram {
private String title;
private TIntIntMap bins = new TIntIntHashMap();
private int maxBin = Integer.MIN_VALUE;
private int minBin = Integer.MAX_VALUE;
private long count = 0;
private int maxVal;
public Histogram (String title) {
this.title = title;
}
public void add (int i) {
count++;
int binVal = bins.adjustOrPutValue(i, 1, 1);
if (binVal > maxVal)
maxVal = binVal;
if (i > maxBin) {
maxBin = i;
}
if (i < minBin) {
minBin = i;
}
}
private static String makeBar (int value, int max) {
final int WIDTH = 20;
int n = value * WIDTH / max;
String bar = Strings.repeat("#", n);
String space = Strings.repeat("_", WIDTH - n);
return bar + space + " ";
}
public void display () {
int[] lessEqual = new int[maxBin + 1];
System.out.println("--- Histogram: " + title + " ---");
System.out.println(" n == <= >");
int sum = 0;
int maxCount = 0;
for (int i = minBin; i <= maxBin; i++) {
int n = bins.get(i);
if (n > maxCount) {
maxCount = n;
}
sum += n;
lessEqual[i] = sum;
}
// Sum now equals the sum of all bins.
for (int i = 0; i <= maxBin; i++) {
if (((double)lessEqual[i]) / sum > 0.999) {
System.out.println("Ending display at 99.9% of total objects.");
break;
}
System.out.printf("%2d: %7d %7d %7d ", i, bins.get(i), lessEqual[i], sum - lessEqual[i]);
System.out.print(makeBar(bins.get(i), maxCount));
System.out.print(makeBar(lessEqual[i], sum));
System.out.print(makeBar(sum - lessEqual[i], sum));
System.out.println();
}
System.out.println();
}
public void displayHorizontal () {
System.out.println("--- Histogram: " + title + " ---");
// TODO: horizontal scale
double vscale = 30d / maxVal;
for (int i = 0; i < 30; i++) {
StringBuilder row = new StringBuilder(maxBin - minBin + 1);
int minValToDisplayThisRow = (int) ((30 - i) / vscale);
for (int j = minBin; j <= maxBin; j++) {
if (bins.get(j) > minValToDisplayThisRow)
row.append('#');
else
row.append(' ');
}
System.out.println(row);
}
// put a mark at zero and at the ends
if (minBin < 0 && maxBin > 0) {
StringBuilder ticks = new StringBuilder();
for (int i = minBin; i < 0; i++)
ticks.append(' ');
ticks.append('|');
System.out.println(ticks);
}
StringBuilder row = new StringBuilder();
for (int i = minBin; i < maxBin; i++) {
row.append(' ');
}
String start = new Integer(minBin).toString();
row.replace(0, start.length(), start);
String end = new Integer(maxBin).toString();
row.replace(row.length() - end.length(), row.length(), end);
System.out.println(row);
}
public int mean() {
long sum = 0;
for (TIntIntIterator it = bins.iterator(); it.hasNext();) {
it.advance();
sum += it.key() * it.value();
}
return (int) (sum / count);
}
public static void main (String... args) {
System.out.println("Testing histogram store with normal distribution, mean 0");
Histogram h = new Histogram("Normal");
MersenneTwister mt = new MersenneTwister();
IntStream.range(0, 1000000).map(i -> (int) Math.round(mt.nextGaussian() * 20 + 2.5)).forEach(h::add);
h.displayHorizontal();
System.out.println("mean: " + h.mean());
}
}