1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.math4.legacy.stat.descriptive.rank;
18
19 import java.text.DecimalFormat;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.List;
25
26 import org.apache.commons.numbers.core.Precision;
27 import org.apache.commons.numbers.arrays.SortInPlace;
28 import org.apache.commons.math4.legacy.analysis.UnivariateFunction;
29 import org.apache.commons.math4.legacy.analysis.interpolation.LinearInterpolator;
30 import org.apache.commons.math4.legacy.analysis.interpolation.NevilleInterpolator;
31 import org.apache.commons.math4.legacy.analysis.interpolation.UnivariateInterpolator;
32 import org.apache.commons.math4.legacy.exception.InsufficientDataException;
33 import org.apache.commons.math4.legacy.exception.OutOfRangeException;
34 import org.apache.commons.math4.legacy.exception.NullArgumentException;
35 import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
36 import org.apache.commons.math4.legacy.stat.descriptive.AbstractStorelessUnivariateStatistic;
37 import org.apache.commons.math4.legacy.stat.descriptive.StorelessUnivariateStatistic;
38
39 /**
40 * A {@link StorelessUnivariateStatistic} estimating percentiles using the
41 * <a href="http://www.cs.wustl.edu/~jain/papers/ftp/psqr.pdf">P<SUP>2</SUP></a>
42 * Algorithm as explained by <a href="http://www.cse.wustl.edu/~jain/">Raj
43 * Jain</a> and Imrich Chlamtac in
44 * <a href="http://www.cse.wustl.edu/~jain/papers/psqr.htm">P<SUP>2</SUP> Algorithm
45 * for Dynamic Calculation of Quantiles and Histogram Without Storing
46 * Observations</a>.
47 * <p>
48 * Note: This implementation is not synchronized and produces an approximate
49 * result. For small samples, where data can be stored and processed in memory,
50 * {@link Percentile} should be used.</p>
51 */
52 public class PSquarePercentile extends AbstractStorelessUnivariateStatistic
53 implements StorelessUnivariateStatistic {
54
55 /**
56 * The maximum array size used for psquare algorithm.
57 */
58 private static final int PSQUARE_CONSTANT = 5;
59
60 /**
61 * A Default quantile needed in case if user prefers to use default no
62 * argument constructor.
63 */
64 private static final double DEFAULT_QUANTILE_DESIRED = 50d;
65
66 /**
67 * A decimal formatter for print convenience.
68 */
69 private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("00.00");
70
71 /**
72 * Initial list of 5 numbers corresponding to 5 markers. <b>NOTE:</b>watch
73 * out for the add methods that are overloaded.
74 */
75 private final List<Double> initialFive = new FixedCapacityList<>(PSQUARE_CONSTANT);
76
77 /**
78 * The quantile needed should be in range of 0-1. The constructor
79 * {@link #PSquarePercentile(double)} ensures that passed in percentile is
80 * divided by 100.
81 */
82 private final double quantile;
83
84 /**
85 * lastObservation is the last observation value/input sample. No need to
86 * serialize
87 */
88 private transient double lastObservation;
89
90 /**
91 * Markers is the marker collection object which comes to effect.
92 * only after 5 values are inserted
93 */
94 private PSquareMarkers markers;
95
96 /**
97 * Computed p value (i,e percentile value of data set hither to received).
98 */
99 private double pValue = Double.NaN;
100
101 /**
102 * Counter to count the values/observations accepted into this data set.
103 */
104 private long countOfObservations;
105
106 /**
107 * Constructs a PSquarePercentile with the specific percentile value.
108 * @param p the percentile
109 * @throws OutOfRangeException if p is not greater than 0 and less
110 * than or equal to 100
111 */
112 public PSquarePercentile(final double p) {
113 if (p > 100 || p < 0) {
114 throw new OutOfRangeException(LocalizedFormats.OUT_OF_RANGE, p, 0, 100);
115 }
116 this.quantile = p / 100d;// always set it within (0,1]
117 }
118
119 /**
120 * Default constructor that assumes a {@link #DEFAULT_QUANTILE_DESIRED
121 * default quantile} needed.
122 */
123 PSquarePercentile() {
124 this(DEFAULT_QUANTILE_DESIRED);
125 }
126
127 /**
128 * {@inheritDoc}
129 */
130 @Override
131 public int hashCode() {
132 double result = getResult();
133 result = Double.isNaN(result) ? 37 : result;
134 final double markersHash = markers == null ? 0 : markers.hashCode();
135 final double[] toHash = {result, quantile, markersHash, countOfObservations};
136 return Arrays.hashCode(toHash);
137 }
138
139 /**
140 * Returns true iff {@code o} is a {@code PSquarePercentile} returning the.
141 * same values as this for {@code getResult()} and {@code getN()} and also
142 * having equal markers
143 *
144 * @param o object to compare
145 * @return true if {@code o} is a {@code PSquarePercentile} with
146 * equivalent internal state
147 */
148 @Override
149 public boolean equals(Object o) {
150 boolean result = false;
151 if (this == o) {
152 result = true;
153 } else if (o instanceof PSquarePercentile) {
154 PSquarePercentile that = (PSquarePercentile) o;
155 boolean isNotNull = markers != null && that.markers != null;
156 boolean isNull = markers == null && that.markers == null;
157 result = isNotNull ? markers.equals(that.markers) : isNull;
158 // markers as in the case of first
159 // five observations
160 result = result && getN() == that.getN();
161 }
162 return result;
163 }
164
165 /**
166 * {@inheritDoc}The internal state updated due to the new value in this
167 * context is basically of the marker positions and computation of the
168 * approximate quantile.
169 *
170 * @param observation the observation currently being added.
171 */
172 @Override
173 public void increment(final double observation) {
174 // Increment counter
175 countOfObservations++;
176
177 // Store last observation
178 this.lastObservation = observation;
179
180 // 0. Use Brute force for <5
181 if (markers == null) {
182 if (initialFive.add(observation)) {
183 Collections.sort(initialFive);
184 pValue =
185 initialFive
186 .get((int) (quantile * (initialFive.size() - 1)));
187 return;
188 }
189 // 1. Initialize once after 5th observation
190 markers = newMarkers(initialFive, quantile);
191 }
192 // 2. process a Data Point and return pValue
193 pValue = markers.processDataPoint(observation);
194 }
195
196 /**
197 * Returns a string containing the last observation, the current estimate
198 * of the quantile and all markers.
199 *
200 * @return string representation of state data
201 */
202 @Override
203 public String toString() {
204
205 if (markers == null) {
206 return String.format("obs=%s pValue=%s",
207 DECIMAL_FORMAT.format(lastObservation),
208 DECIMAL_FORMAT.format(pValue));
209 } else {
210 return String.format("obs=%s markers=%s",
211 DECIMAL_FORMAT.format(lastObservation), markers.toString());
212 }
213 }
214
215 /**
216 * {@inheritDoc}
217 */
218 @Override
219 public long getN() {
220 return countOfObservations;
221 }
222
223 /**
224 * {@inheritDoc}
225 */
226 @Override
227 public StorelessUnivariateStatistic copy() {
228 // multiply quantile by 100 now as anyway constructor divides it by 100
229 PSquarePercentile copy = new PSquarePercentile(100d * quantile);
230
231 if (markers != null) {
232 copy.markers = markers.copy();
233 }
234 copy.countOfObservations = countOfObservations;
235 copy.pValue = pValue;
236 copy.initialFive.clear();
237 copy.initialFive.addAll(initialFive);
238
239 return copy;
240 }
241
242 /**
243 * Returns the quantile estimated by this statistic in the range [0.0-1.0].
244 *
245 * @return quantile estimated by {@link #getResult()}
246 */
247 public double quantile() {
248 return quantile;
249 }
250
251 /**
252 * {@inheritDoc}. This basically clears all the markers, the
253 * initialFive list and sets countOfObservations to 0.
254 */
255 @Override
256 public void clear() {
257 markers = null;
258 initialFive.clear();
259 countOfObservations = 0L;
260 pValue = Double.NaN;
261 }
262
263 /**
264 * {@inheritDoc}
265 */
266 @Override
267 public double getResult() {
268 if (Double.compare(quantile, 1d) == 0) {
269 pValue = maximum();
270 } else if (Double.compare(quantile, 0d) == 0) {
271 pValue = minimum();
272 }
273 return pValue;
274 }
275
276 /**
277 * @return maximum in the data set added to this statistic
278 */
279 private double maximum() {
280 double val = Double.NaN;
281 if (markers != null) {
282 val = markers.height(PSQUARE_CONSTANT);
283 } else if (!initialFive.isEmpty()) {
284 val = initialFive.get(initialFive.size() - 1);
285 }
286 return val;
287 }
288
289 /**
290 * @return minimum in the data set added to this statistic
291 */
292 private double minimum() {
293 double val = Double.NaN;
294 if (markers != null) {
295 val = markers.height(1);
296 } else if (!initialFive.isEmpty()) {
297 val = initialFive.get(0);
298 }
299 return val;
300 }
301
302 /**
303 * Markers is an encapsulation of the five markers/buckets as indicated in
304 * the original works.
305 */
306 private static final class Markers implements PSquareMarkers {
307 /** Low marker index. */
308 private static final int LOW = 2;
309
310 /** High marker index. */
311 private static final int HIGH = 4;
312
313 /**
314 * Array of 5+1 Markers (The first marker is dummy just so we.
315 * can match the rest of indexes [1-5] indicated in the original works
316 * which follows unit based index)
317 */
318 private final Marker[] markerArray;
319
320 /**
321 * Kth cell belonging to [1-5] of the markerArray. No need for
322 * this to be serialized
323 */
324 private transient int k = -1;
325
326 /**
327 * Constructor.
328 *
329 * @param theMarkerArray marker array to be used
330 */
331 private Markers(final Marker[] theMarkerArray) {
332 NullArgumentException.check(theMarkerArray);
333 markerArray = theMarkerArray;
334 for (int i = 1; i < PSQUARE_CONSTANT; i++) {
335 markerArray[i].previous(markerArray[i - 1])
336 .next(markerArray[i + 1]).index(i);
337 }
338 markerArray[0].previous(markerArray[0]).next(markerArray[1])
339 .index(0);
340 markerArray[5].previous(markerArray[4]).next(markerArray[5])
341 .index(5);
342 }
343
344 /**
345 * Constructor.
346 *
347 * @param initialFive elements required to build Marker
348 * @param p quantile required to be computed
349 */
350 private Markers(final List<Double> initialFive, final double p) {
351 this(createMarkerArray(initialFive, p));
352 }
353
354 /**
355 * Creates a marker array using initial five elements and a quantile.
356 *
357 * @param initialFive list of initial five elements
358 * @param p the pth quantile
359 * @return Marker array
360 */
361 private static Marker[] createMarkerArray(
362 final List<Double> initialFive, final double p) {
363 final int countObserved =
364 initialFive == null ? -1 : initialFive.size();
365 if (countObserved < PSQUARE_CONSTANT) {
366 throw new InsufficientDataException(
367 LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE,
368 countObserved, PSQUARE_CONSTANT);
369 }
370 Collections.sort(initialFive);
371 return new Marker[] {
372 new Marker(),// Null Marker
373 new Marker(initialFive.get(0), 1, 0, 1),
374 new Marker(initialFive.get(1), 1 + 2 * p, p / 2, 2),
375 new Marker(initialFive.get(2), 1 + 4 * p, p, 3),
376 new Marker(initialFive.get(3), 3 + 2 * p, (1 + p) / 2, 4),
377 new Marker(initialFive.get(4), 5, 1, 5) };
378 }
379
380 /**
381 * {@inheritDoc}
382 */
383 @Override
384 public int hashCode() {
385 return Arrays.deepHashCode(markerArray);
386 }
387
388 /**
389 * {@inheritDoc}.This equals method basically checks for marker array to
390 * be deep equals.
391 *
392 * @param o is the other object
393 * @return true if the object compares with this object are equivalent
394 */
395 @Override
396 public boolean equals(Object o) {
397 boolean result = false;
398 if (this == o) {
399 result = true;
400 } else if (o instanceof Markers) {
401 Markers that = (Markers) o;
402 result = Arrays.deepEquals(markerArray, that.markerArray);
403 }
404 return result;
405 }
406
407 /**
408 * Process a data point.
409 *
410 * @param inputDataPoint is the data point passed
411 * @return computed percentile
412 */
413 @Override
414 public double processDataPoint(final double inputDataPoint) {
415
416 // 1. Find cell and update minima and maxima
417 final int kthCell = findCellAndUpdateMinMax(inputDataPoint);
418
419 // 2. Increment positions
420 incrementPositions(1, kthCell + 1, 5);
421
422 // 2a. Update desired position with increments
423 updateDesiredPositions();
424
425 // 3. Adjust heights of m[2-4] if necessary
426 adjustHeightsOfMarkers();
427
428 // 4. Return percentile
429 return getPercentileValue();
430 }
431
432 /**
433 * Returns the percentile computed thus far.
434 *
435 * @return height of mid point marker
436 */
437 @Override
438 public double getPercentileValue() {
439 return height(3);
440 }
441
442 /**
443 * Finds the cell where the input observation / value fits.
444 *
445 * @param observation the input value to be checked for
446 * @return kth cell (of the markers ranging from 1-5) where observed
447 * sample fits
448 */
449 private int findCellAndUpdateMinMax(final double observation) {
450 k = -1;
451 if (observation < height(1)) {
452 markerArray[1].markerHeight = observation;
453 k = 1;
454 } else if (observation < height(2)) {
455 k = 1;
456 } else if (observation < height(3)) {
457 k = 2;
458 } else if (observation < height(4)) {
459 k = 3;
460 } else if (observation <= height(5)) {
461 k = 4;
462 } else {
463 markerArray[5].markerHeight = observation;
464 k = 4;
465 }
466 return k;
467 }
468
469 /**
470 * Adjust marker heights by setting quantile estimates to middle markers.
471 */
472 private void adjustHeightsOfMarkers() {
473 for (int i = LOW; i <= HIGH; i++) {
474 estimate(i);
475 }
476 }
477
478 /**
479 * {@inheritDoc}
480 */
481 @Override
482 public double estimate(final int index) {
483 if (index < LOW || index > HIGH) {
484 throw new OutOfRangeException(index, LOW, HIGH);
485 }
486 return markerArray[index].estimate();
487 }
488
489 /**
490 * Increment positions by d. Refer to algorithm paper for the
491 * definition of d.
492 *
493 * @param d The increment value for the position
494 * @param startIndex start index of the marker array
495 * @param endIndex end index of the marker array
496 */
497 private void incrementPositions(final int d, final int startIndex,
498 final int endIndex) {
499 for (int i = startIndex; i <= endIndex; i++) {
500 markerArray[i].incrementPosition(d);
501 }
502 }
503
504 /**
505 * Desired positions incremented by bucket width. The bucket width is
506 * basically the desired increments.
507 */
508 private void updateDesiredPositions() {
509 for (int i = 1; i < markerArray.length; i++) {
510 markerArray[i].updateDesiredPosition();
511 }
512 }
513
514 /**
515 * Return marker height given index.
516 *
517 * @param markerIndex index of marker within (1,6)
518 * @return marker height
519 */
520 @Override
521 public double height(final int markerIndex) {
522 if (markerIndex >= markerArray.length || markerIndex <= 0) {
523 throw new OutOfRangeException(markerIndex, 1, markerArray.length);
524 }
525 return markerArray[markerIndex].markerHeight;
526 }
527
528 /**
529 * Copy markers.
530 *
531 * @return a new instance.
532 */
533 public Markers copy() {
534 return new Markers(new Marker[] {
535 new Marker(),
536 markerArray[1].copy(),
537 markerArray[2].copy(),
538 markerArray[3].copy(),
539 markerArray[4].copy(),
540 markerArray[5].copy()
541 });
542 }
543
544 /**
545 * Returns string representation of the Marker array.
546 *
547 * @return Markers as a string
548 */
549 @Override
550 public String toString() {
551 return String.format("m1=[%s],m2=[%s],m3=[%s],m4=[%s],m5=[%s]",
552 markerArray[1].toString(), markerArray[2].toString(),
553 markerArray[3].toString(), markerArray[4].toString(),
554 markerArray[5].toString());
555 }
556 }
557
558 /**
559 * The class modeling the attributes of the marker of the P-square algorithm.
560 */
561 private static final class Marker {
562 /**
563 * The marker index which is just a serial number for the marker in the
564 * marker array of 5+1.
565 */
566 private int index;
567
568 /**
569 * The integral marker position. Refer to the variable n in the original
570 * works.
571 */
572 private double intMarkerPosition;
573
574 /**
575 * Desired marker position. Refer to the variable n' in the original
576 * works.
577 */
578 private double desiredMarkerPosition;
579
580 /**
581 * Marker height or the quantile. Refer to the variable q in the
582 * original works.
583 */
584 private double markerHeight;
585
586 /**
587 * Desired marker increment. Refer to the variable dn' in the original
588 * works.
589 */
590 private double desiredMarkerIncrement;
591
592 /**
593 * Next and previous markers for easy linked navigation in loops. this
594 * is not serialized as they can be rebuilt during deserialization.
595 */
596 private transient Marker next;
597
598 /**
599 * The previous marker links.
600 */
601 private transient Marker previous;
602
603 /**
604 * Nonlinear interpolator.
605 */
606 private final UnivariateInterpolator nonLinear = new NevilleInterpolator();
607
608 /**
609 * Linear interpolator which is not serializable.
610 */
611 private transient UnivariateInterpolator linear = new LinearInterpolator();
612
613 /**
614 * Default constructor.
615 */
616 private Marker() {
617 this.next = this.previous = this;
618 }
619
620 /**
621 * Constructor of the marker with parameters.
622 *
623 * @param heightOfMarker represent the quantile value
624 * @param makerPositionDesired represent the desired marker position
625 * @param markerPositionIncrement represent increments for position
626 * @param markerPositionNumber represent the position number of marker
627 */
628 private Marker(double heightOfMarker, double makerPositionDesired,
629 double markerPositionIncrement, double markerPositionNumber) {
630 this();
631 this.markerHeight = heightOfMarker;
632 this.desiredMarkerPosition = makerPositionDesired;
633 this.desiredMarkerIncrement = markerPositionIncrement;
634 this.intMarkerPosition = markerPositionNumber;
635 }
636
637 /**
638 * Sets the previous marker.
639 *
640 * @param previousMarker the previous marker to the current marker in
641 * the array of markers
642 * @return this instance
643 */
644 private Marker previous(final Marker previousMarker) {
645 NullArgumentException.check(previousMarker);
646 this.previous = previousMarker;
647 return this;
648 }
649
650 /**
651 * Sets the next marker.
652 *
653 * @param nextMarker the next marker to the current marker in the array
654 * of markers
655 * @return this instance
656 */
657 private Marker next(final Marker nextMarker) {
658 NullArgumentException.check(nextMarker);
659 this.next = nextMarker;
660 return this;
661 }
662
663 /**
664 * Sets the index of the marker.
665 *
666 * @param indexOfMarker the array index of the marker in marker array
667 * @return this instance
668 */
669 private Marker index(final int indexOfMarker) {
670 this.index = indexOfMarker;
671 return this;
672 }
673
674 /**
675 * Update desired Position with increment.
676 */
677 private void updateDesiredPosition() {
678 desiredMarkerPosition += desiredMarkerIncrement;
679 }
680
681 /**
682 * Increment Position by d.
683 *
684 * @param d a delta value to increment
685 */
686 private void incrementPosition(final int d) {
687 intMarkerPosition += d;
688 }
689
690 /**
691 * Difference between desired and actual position.
692 *
693 * @return difference between desired and actual position
694 */
695 private double difference() {
696 return desiredMarkerPosition - intMarkerPosition;
697 }
698
699 /**
700 * Estimate the quantile for the current marker.
701 *
702 * @return estimated quantile
703 */
704 private double estimate() {
705 final double di = difference();
706 final boolean isNextHigher =
707 next.intMarkerPosition - intMarkerPosition > 1;
708 final boolean isPreviousLower =
709 previous.intMarkerPosition - intMarkerPosition < -1;
710
711 if (di >= 1 && isNextHigher || di <= -1 && isPreviousLower) {
712 final int d = di >= 0 ? 1 : -1;
713 final double[] xval =
714 new double[] { previous.intMarkerPosition,
715 intMarkerPosition, next.intMarkerPosition };
716 final double[] yval =
717 new double[] { previous.markerHeight, markerHeight,
718 next.markerHeight };
719 final double xD = intMarkerPosition + d;
720
721 UnivariateFunction univariateFunction =
722 nonLinear.interpolate(xval, yval);
723 markerHeight = univariateFunction.value(xD);
724
725 // If parabolic estimate is bad then turn linear
726 if (isEstimateBad(yval, markerHeight)) {
727 int delta = xD - xval[1] > 0 ? 1 : -1;
728 final double[] xBad =
729 new double[] { xval[1], xval[1 + delta] };
730 final double[] yBad =
731 new double[] { yval[1], yval[1 + delta] };
732 SortInPlace.ASCENDING.apply(xBad, yBad);// since d can be +/- 1
733 univariateFunction = linear.interpolate(xBad, yBad);
734 markerHeight = univariateFunction.value(xD);
735 }
736 incrementPosition(d);
737 }
738 return markerHeight;
739 }
740
741 /**
742 * Check if parabolic/nonlinear estimate is bad by checking if the
743 * ordinate found is beyond the y[0] and y[2].
744 *
745 * @param y the array to get the bounds
746 * @param yD the estimate
747 * @return true if yD is a bad estimate
748 */
749 private boolean isEstimateBad(final double[] y, final double yD) {
750 return yD <= y[0] || yD >= y[2];
751 }
752
753 /**
754 * {@inheritDoc}<i>This equals method checks for marker attributes and
755 * as well checks if navigation pointers (next and previous) are the same
756 * between this and passed in object</i>
757 *
758 * @param o Other object
759 * @return true if this equals passed in other object o
760 */
761 @Override
762 public boolean equals(Object o) {
763 boolean result = false;
764 if (this == o) {
765 result = true;
766 } else if (o instanceof Marker) {
767 Marker that = (Marker) o;
768
769 result = Double.compare(markerHeight, that.markerHeight) == 0;
770 result =
771 result &&
772 Double.compare(intMarkerPosition,
773 that.intMarkerPosition) == 0;
774 result =
775 result &&
776 Double.compare(desiredMarkerPosition,
777 that.desiredMarkerPosition) == 0;
778 result =
779 result &&
780 Double.compare(desiredMarkerIncrement,
781 that.desiredMarkerIncrement) == 0;
782
783 result = result && next.index == that.next.index;
784 result = result && previous.index == that.previous.index;
785 }
786 return result;
787 }
788
789 /** {@inheritDoc} */
790 @Override
791 public int hashCode() {
792 return Arrays.hashCode(new double[] {markerHeight, intMarkerPosition,
793 desiredMarkerIncrement, desiredMarkerPosition, previous.index, next.index});
794 }
795
796 /**
797 * Copy this instance.
798 *
799 * @return a new instance.
800 */
801 public Marker copy() {
802 return new Marker(markerHeight, desiredMarkerPosition, desiredMarkerIncrement, intMarkerPosition);
803 }
804
805 /**
806 * {@inheritDoc}
807 */
808 @Override
809 public String toString() {
810 return String.format(
811 "index=%.0f,n=%.0f,np=%.2f,q=%.2f,dn=%.2f,prev=%d,next=%d",
812 (double) index, Precision.round(intMarkerPosition, 0),
813 Precision.round(desiredMarkerPosition, 2),
814 Precision.round(markerHeight, 2),
815 Precision.round(desiredMarkerIncrement, 2), previous.index,
816 next.index);
817 }
818 }
819
820 /**
821 * A simple fixed capacity list that has an upper bound to growth.
822 * Once its capacity is reached, {@code add} is a no-op, returning
823 * {@code false}.
824 *
825 * @param <E> type for fixed capacity list
826 */
827 private static final class FixedCapacityList<E> extends ArrayList<E> {
828 /**
829 * Capacity of the list.
830 */
831 private final int capacity;
832
833 /**
834 * This constructor constructs the list with given capacity and as well.
835 * as stores the capacity
836 *
837 * @param fixedCapacity the capacity to be fixed for this list
838 */
839 FixedCapacityList(final int fixedCapacity) {
840 super(fixedCapacity);
841 this.capacity = fixedCapacity;
842 }
843
844 /**
845 * {@inheritDoc} In addition it checks if the {@link #size()} returns a
846 * size that is within capacity and if true it adds; otherwise the list
847 * contents are unchanged and {@code false} is returned.
848 *
849 * @return true if addition is successful and false otherwise
850 */
851 @Override
852 public boolean add(final E e) {
853 return size() < capacity && super.add(e);
854 }
855
856 /**
857 * {@inheritDoc} In addition it checks if the sum of Collection size and
858 * this instance's {@link #size()} returns a value that is within
859 * capacity and if true it adds the collection; otherwise the list
860 * contents are unchanged and {@code false} is returned.
861 *
862 * @return true if addition is successful and false otherwise
863 */
864 @Override
865 public boolean addAll(Collection<? extends E> collection) {
866 boolean isCollectionLess =
867 collection != null &&
868 collection.size() + size() <= capacity;
869 return isCollectionLess && super.addAll(collection);
870 }
871 }
872
873 /**
874 * A creation method to build Markers.
875 *
876 * @param initialFive list of initial five elements
877 * @param p the quantile desired
878 * @return an instance of PSquareMarkers
879 */
880 public static PSquareMarkers newMarkers(final List<Double> initialFive, final double p) {
881 return new Markers(initialFive, p);
882 }
883
884 /**
885 * An interface that encapsulates abstractions of the
886 * P-square algorithm markers as is explained in the original works.
887 */
888 interface PSquareMarkers { // Package-private for unit testing.
889 /**
890 * Returns Percentile value computed thus far.
891 *
892 * @return percentile
893 */
894 double getPercentileValue();
895
896 /**
897 * Returns the marker height (or percentile) of a given marker index.
898 *
899 * @param markerIndex is the index of marker in the marker array
900 * @return percentile value of the marker index passed
901 * @throws OutOfRangeException in case the index is not within [1-5]
902 */
903 double height(int markerIndex);
904
905 /**
906 * Copy factory.
907 *
908 * @return a new instance
909 */
910 PSquareMarkers copy();
911
912 /**
913 * Process a data point by moving the marker heights based on estimator.
914 *
915 * @param inputDataPoint is the data point passed
916 * @return computed percentile
917 */
918 double processDataPoint(double inputDataPoint);
919
920 /**
921 * An Estimate of the percentile value of a given Marker.
922 *
923 * @param index the marker's index in the array of markers
924 * @return percentile estimate
925 * @throws OutOfRangeException in case if index is not within [1-5]
926 */
927 double estimate(int index);
928 }
929 }