KthSelector.java

  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. import java.util.Arrays;

  19. import org.apache.commons.math4.legacy.exception.NullArgumentException;
  20. import org.apache.commons.math4.core.jdkmath.JdkMath;

  21. /**
  22.  * A Simple K<sup>th</sup> selector implementation to pick up the
  23.  * K<sup>th</sup> ordered element from a work array containing the input
  24.  * numbers.
  25.  * @since 3.4
  26.  */
  27. public class KthSelector {
  28.     /** Minimum selection size for insertion sort rather than selection. */
  29.     private static final int MIN_SELECT_SIZE = 15;

  30.     /** A {@link PivotingStrategy} used for pivoting. */
  31.     private final PivotingStrategy pivotingStrategy;

  32.     /**
  33.      * Constructor with default {@link MedianOf3PivotingStrategy median of 3} pivoting strategy.
  34.      */
  35.     public KthSelector() {
  36.         this.pivotingStrategy = new MedianOf3PivotingStrategy();
  37.     }

  38.     /**
  39.      * Constructor with specified pivoting strategy.
  40.      *
  41.      * @param pivotingStrategy pivoting strategy to use
  42.      * @throws NullArgumentException when pivotingStrategy is null
  43.      * @see MedianOf3PivotingStrategy
  44.      * @see RandomPivotingStrategy
  45.      * @see CentralPivotingStrategy
  46.      */
  47.     public KthSelector(final PivotingStrategy pivotingStrategy) {
  48.         NullArgumentException.check(pivotingStrategy);
  49.         this.pivotingStrategy = pivotingStrategy;
  50.     }

  51.     /** Get the pivoting strategy.
  52.      * @return pivoting strategy
  53.      */
  54.     public PivotingStrategy getPivotingStrategy() {
  55.         return pivotingStrategy;
  56.     }

  57.     /**
  58.      * Select K<sup>th</sup> value in the array.
  59.      *
  60.      * @param work work array to use to find out the K<sup>th</sup> value
  61.      * @param pivotsHeap cached pivots heap that can be used for efficient estimation
  62.      * @param k the index whose value in the array is of interest
  63.      * @return K<sup>th</sup> value
  64.      */
  65.     public double select(final double[] work, final int[] pivotsHeap, final int k) {
  66.         int begin = 0;
  67.         int end = work.length;
  68.         int node = 0;
  69.         final boolean usePivotsHeap = pivotsHeap != null;
  70.         while (end - begin > MIN_SELECT_SIZE) {
  71.             final int pivot;

  72.             if (usePivotsHeap && node < pivotsHeap.length &&
  73.                     pivotsHeap[node] >= 0) {
  74.                 // the pivot has already been found in a previous call
  75.                 // and the array has already been partitioned around it
  76.                 pivot = pivotsHeap[node];
  77.             } else {
  78.                 // select a pivot and partition work array around it
  79.                 pivot = partition(work, begin, end, pivotingStrategy.pivotIndex(work, begin, end));
  80.                 if (usePivotsHeap && node < pivotsHeap.length) {
  81.                     pivotsHeap[node] = pivot;
  82.                 }
  83.             }

  84.             if (k == pivot) {
  85.                 // the pivot was exactly the element we wanted
  86.                 return work[k];
  87.             } else if (k < pivot) {
  88.                 // the element is in the left partition
  89.                 end  = pivot;
  90.                 node = JdkMath.min(2 * node + 1, usePivotsHeap ? pivotsHeap.length : end);
  91.             } else {
  92.                 // the element is in the right partition
  93.                 begin = pivot + 1;
  94.                 node  = JdkMath.min(2 * node + 2, usePivotsHeap ? pivotsHeap.length : end);
  95.             }
  96.         }
  97.         Arrays.sort(work, begin, end);
  98.         return work[k];
  99.     }

  100.     /**
  101.      * Partition an array slice around a pivot.Partitioning exchanges array
  102.      * elements such that all elements smaller than pivot are before it and
  103.      * all elements larger than pivot are after it.
  104.      *
  105.      * @param work work array
  106.      * @param begin index of the first element of the slice of work array
  107.      * @param end index after the last element of the slice of work array
  108.      * @param pivot initial index of the pivot
  109.      * @return index of the pivot after partition
  110.      */
  111.     private int partition(final double[] work, final int begin, final int end, final int pivot) {

  112.         final double value = work[pivot];
  113.         work[pivot] = work[begin];

  114.         int i = begin + 1;
  115.         int j = end - 1;
  116.         while (i < j) {
  117.             while (i < j && Double.compare(work[j], value) > 0) {
  118.                 --j;
  119.             }
  120.             while (i < j && Double.compare(work[i], value) < 0) {
  121.                 ++i;
  122.             }

  123.             if (i < j) {
  124.                 final double tmp = work[i];
  125.                 work[i++] = work[j];
  126.                 work[j--] = tmp;
  127.             }
  128.         }

  129.         if (i >= end || Double.compare(work[i], value) > 0) {
  130.             --i;
  131.         }
  132.         work[begin] = work[i];
  133.         work[i] = value;
  134.         return i;
  135.     }
  136. }