org.opentripplanner.profile.Stats Maven / Gradle / Ivy
package org.opentripplanner.profile;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
import org.opentripplanner.routing.edgetype.TripPattern;
import org.opentripplanner.routing.trippattern.FrequencyEntry;
import org.opentripplanner.routing.trippattern.TripTimes;
import java.util.Collection;
import java.util.List;
/**
* num may be 0 if there are no observations.
* num will become 1 when adding a scalar or another Stats.
*/
class Stats implements Cloneable {
public int min = 0;
public int avg = 0;
public int max = 0;
public int num = 0;
/** Construct a new empty Stats containing no values. */
public Stats () { }
/** Construct a new Stats for a single int value. */
public Stats (int loneValue) {
min = loneValue;
max = loneValue;
avg = loneValue;
num = 1;
}
/** Construct a new Stats summarizing the given list of ints. */
public Stats (int... values) {
this(Ints.asList(values));
}
/** Copy constructor. */
public Stats (Stats other) {
if (other != null) {
this.min = other.min;
this.avg = other.avg;
this.max = other.max;
}
}
/**
* Adds another Stats into this one in place. This is intended to combine them in series, as for legs of a journey.
* It is not really correct for the average, but min and max values hold and avg is still a useful indicator.
* @return void to avoid thinking that a new object is created.
*/
public void add(Stats s) { // TODO maybe should be called 'chain' rather than add
min += s.min;
max += s.max;
avg += s.avg; // This only makes sense when adding successive legs TODO think through in depth
num = 1; // Num is poorly defined once addition has occurred
}
/** Like add(Stats) but min, max, and avg are all equal. */
public void add(int x) {
min += x;
avg += x;
max += x;
num = 1; // it's poorly defined here
}
/**
* Takes StreetSegments for each different access/egress mode and creates a stats describing the range of
* access/egress times present.
*/
public void add(Collection segs) {
if (segs == null || segs.isEmpty()) return;
List times = Lists.newArrayList();
for (StreetSegment seg : segs) times.add(seg.time);
Stats s = new Stats(times);
add(s);
}
/**
* Combines another Stats into this one in place. This considers the two Stats to be parallel, as for various trips
* or patterns making up a single leg of a journey. In this case, the weighted average is correctly computed.
* @return void to avoid thinking that a new object is created.
*/
public void merge (Stats other) {
if (other.min < min) min = other.min;
if (other.max > max) max = other.max;
avg = (avg * num + other.avg * other.num) / (num + other.num); // TODO should be float math
// FIXME num is not updated?
}
/**
* Combines a single value into this stats in place.
* @return void to indicate that a new object is NOT created.
*/
public void merge (int other) {
if (other < min) min = other;
if (other > max) max = other;
avg = (avg * num + other) / (num + 1); // TODO should be float math
num += 1;
}
/** Build a composite Stats out of a bunch of other Stats. They are combined in parallel, as in merge(Stats). */
public Stats (Iterable stats) {
min = Integer.MAX_VALUE;
num = 0;
for (Stats other : stats) {
if (other.min < min) min = other.min;
if (other.max > max) max = other.max;
avg += other.avg * other.num;
num += other.num;
}
avg /= num; // TODO should perhaps be float math
}
/** Construct a Stats containing the min, max, average, and count of the given ints. */
public Stats (Collection ints) {
if (ints == null || ints.isEmpty()) throw new AssertionError("Stats are undefined if there are no values.");
min = Integer.MAX_VALUE;
double accumulated = 0;
for (int i : ints) {
if (i > max) max = i;
if (i < min) min = i;
accumulated += i;
}
num = ints.size();
avg = (int) (accumulated / num);
}
public void dump() {
System.out.printf("min %d avg %d max %d\n", min, avg, max);
}
/** Scan through all trips on this pattern and summarize those that are running. */
public static Stats create (TripPattern pattern, int stop0, int stop1, TimeWindow window) {
Stats s = new Stats ();
s.min = Integer.MAX_VALUE;
s.num = 0;
/* Scan through all non-frequency trips accumulating them into stats. */
// TODO maybe we should prefilter the triptimes so we aren't constantly iterating over
// the trips whose service is not running
for (TripTimes tripTimes : pattern.scheduledTimetable.tripTimes) {
int depart = tripTimes.getDepartureTime(stop0);
int arrive = tripTimes.getArrivalTime(stop1);
if (window.includes (depart) &&
window.includes (arrive) &&
window.servicesRunning.get(tripTimes.serviceCode)) {
int t = arrive - depart;
if (t < s.min) s.min = t;
if (t > s.max) s.max = t;
s.avg += t;
++s.num;
}
}
/* Do the same thing for any frequency-based trips. */
for (FrequencyEntry freq : pattern.scheduledTimetable.frequencyEntries) {
TripTimes tt = freq.tripTimes;
int overlap = window.overlap(freq.startTime, freq.endTime, tt.serviceCode);
if (overlap == 0) continue;
int n = overlap / freq.headway + 1; // number of trip instances in the overlap. round up, avoid zeros.
int depart = tt.getDepartureTime(stop0);
int arrive = tt.getArrivalTime(stop1);
int t = arrive - depart;
if (t < s.min) s.min = t;
if (t > s.max) s.max = t;
s.avg += (t * n);
s.num += n;
}
if (s.num > 0) {
s.avg /= s.num;
return s;
}
/* There are no running trips within the time range, on the given serviceIds. */
return null;
}
@Override
public String toString() {
return String.format("min=%.1f avg=%.1f max=%.1f", min/60.0, avg/60.0, max/60.0);
}
}