All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.solr.analytics.util.PercentileCalculator Maven / Gradle / Ivy

There is a newer version: 9.7.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */
package org.apache.solr.analytics.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class PercentileCalculator {
  /**
   * Calculates a list of percentile values for a given list of objects and percentiles.
   *
   * @param list     The list of {@link Comparable} objects to calculate the percentiles of.
   * @param percents The array of percentiles (.01 to .99) to calculate.
   * @return a list of comparables
   */
  public static > List getPercentiles(List list, double[] percents) {
    int size = list.size();
    if (size == 0) {
      return null;
    }

    int[] percs = new int[percents.length];
    for (int i = 0; i < percs.length; i++) {
      percs[i] = (int) Math.round(percents[i] * size - .5);
    }
    int[] percentiles = Arrays.copyOf(percs, percs.length);
    Arrays.sort(percentiles);

    if (percentiles[0] < 0 || percentiles[percentiles.length - 1] > size - 1) {
      throw new IllegalArgumentException();
    }

    List results = new ArrayList<>(percs.length);

    distributeAndFind(list, percentiles, 0, percentiles.length - 1);

    for (int i = 0; i < percs.length; i++) {
      results.add(list.get(percs[i]));
    }
    return results;
  }

  private static > void distributeAndFind(List list, int[] percentiles, int beginIdx, int endIdx) {
    if (endIdx < beginIdx) {
      return;
    }
    int middleIdxb = beginIdx;
    int middleIdxe = beginIdx;
    int begin = (beginIdx == 0) ? -1 : percentiles[beginIdx - 1];
    int end = (endIdx == percentiles.length - 1) ? list.size() : percentiles[endIdx + 1];
    double middle = (begin + end) / 2.0;
    for (int i = beginIdx; i <= endIdx; i++) {
      double value = Math.abs(percentiles[i] - middle) - Math.abs(percentiles[middleIdxb] - middle);
      if (percentiles[i] == percentiles[middleIdxb]) {
        middleIdxe = i;
      } else if (value < 0) {
        middleIdxb = i;
        do {
          middleIdxe = i;
          i++;
        } while (i <= endIdx && percentiles[middleIdxb] == percentiles[i]);
        break;
      }
    }

    int middlePlace = percentiles[middleIdxb];
    int beginPlace = begin + 1;
    int endPlace = end - 1;

    select(list, middlePlace, beginPlace, endPlace);
    distributeAndFind(list, percentiles, beginIdx, middleIdxb - 1);
    distributeAndFind(list, percentiles, middleIdxe + 1, endIdx);
  }

  private static > void select(List list, int place, int begin, int end) {
    T split;
    if (end - begin < 10) {
      split = list.get((int) (Math.random() * (end - begin + 1)) + begin);
    } else {
      split = split(list, begin, end);
    }

    Point result = partition(list, begin, end, split);

    if (place <= result.low) {
      select(list, place, begin, result.low);
    } else if (place >= result.high) {
      select(list, place, result.high, end);
    }
  }

  private static > T split(List list, int begin, int end) {
    T temp;
    int num = (end - begin + 1);
    int recursiveSize = (int) Math.sqrt((double) num);
    int step = num / recursiveSize;
    for (int i = 1; i < recursiveSize; i++) {
      int swapFrom = i * step + begin;
      int swapTo = i + begin;
      temp = list.get(swapFrom);
      list.set(swapFrom, list.get(swapTo));
      list.set(swapTo, temp);
    }
    recursiveSize--;
    select(list, recursiveSize / 2 + begin, begin, recursiveSize + begin);
    return list.get(recursiveSize / 2 + begin);
  }

  private static > Point partition(List list, int begin, int end, T indexElement) {
    T temp;
    int left, right;
    for (left = begin, right = end; left <= right; left++, right--) {
      while (list.get(left).compareTo(indexElement) < 0) {
        left++;
      }
      while (right != begin - 1 && list.get(right).compareTo(indexElement) >= 0) {
        right--;
      }
      if (right <= left) {
        left--;
        right++;
        break;
      }
      temp = list.get(left);
      list.set(left, list.get(right));
      list.set(right, temp);
    }
    while (left > begin - 1 && list.get(left).compareTo(indexElement) >= 0) {
      left--;
    }
    while (right < end + 1 && list.get(right).compareTo(indexElement) <= 0) {
      right++;
    }
    int rightMove = right + 1;
    while (rightMove < end + 1) {
      if (list.get(rightMove).equals(indexElement)) {
        temp = list.get(rightMove);
        list.set(rightMove, list.get(right));
        list.set(right, temp);
        do {
          right++;
        } while (list.get(right).equals(indexElement));
        if (rightMove <= right) {
          rightMove = right;
        }
      }
      rightMove++;
    }
    return new Point(left, right);
  }
}

class Point {
  public int low;
  public int high;

  public Point(int low, int high) {
    this.low = low;
    this.high = high;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy