KeyUpdatingInterval.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.numbers.arrays;

  18. /**
  19.  * An {@link UpdatingInterval} backed by an array of ordered keys.
  20.  *
  21.  * @since 1.2
  22.  */
  23. final class KeyUpdatingInterval implements UpdatingInterval {
  24.     /** Size to use a scan of the keys when splitting instead of binary search.
  25.      * Note binary search has an overhead on small size due to the random left/right
  26.      * branching per iteration. It is much faster on very large sizes. */
  27.     private static final int SCAN_SIZE = 256;

  28.     /** The ordered keys. */
  29.     private final int[] keys;
  30.     /** Index of the left key. */
  31.     private int l;
  32.     /** Index of the right key. */
  33.     private int r;

  34.     /**
  35.      * Create an instance with the provided {@code indices}.
  36.      *
  37.      * <p><strong>Warning:</strong> Indices must be sorted and distinct.
  38.      *
  39.      * @param indices Indices.
  40.      * @param n Number of indices.
  41.      */
  42.     KeyUpdatingInterval(int[] indices, int n) {
  43.         this(indices, 0, n - 1);
  44.     }

  45.     /**
  46.      * @param indices Indices.
  47.      * @param l Index of left key.
  48.      * @param r Index of right key.
  49.      */
  50.     private KeyUpdatingInterval(int[] indices, int l, int r) {
  51.         keys = indices;
  52.         this.l = l;
  53.         this.r = r;
  54.     }

  55.     @Override
  56.     public int left() {
  57.         return keys[l];
  58.     }

  59.     @Override
  60.     public int right() {
  61.         return keys[r];
  62.     }

  63.     @Override
  64.     public int updateLeft(int k) {
  65.         // Assume left < k <= right (i.e. we must move left at least 1)
  66.         // Search using a scan on the assumption that k is close to the end
  67.         int i = l;
  68.         do {
  69.             ++i;
  70.         } while (keys[i] < k);
  71.         l = i;
  72.         return keys[i];
  73.     }

  74.     @Override
  75.     public int updateRight(int k) {
  76.         // Assume left <= k < right (i.e. we must move right at least 1)
  77.         // Search using a scan on the assumption that k is close to the end
  78.         int i = r;
  79.         do {
  80.             --i;
  81.         } while (keys[i] > k);
  82.         r = i;
  83.         return keys[i];
  84.     }

  85.     @Override
  86.     public UpdatingInterval splitLeft(int ka, int kb) {
  87.         // left < ka <= kb < right

  88.         // Find the new left bound for the upper interval.
  89.         // Switch to a linear scan if length is small.
  90.         int i;
  91.         if (r - l < SCAN_SIZE) {
  92.             i = r;
  93.             do {
  94.                 --i;
  95.             } while (keys[i] > kb);
  96.         } else {
  97.             // Binary search
  98.             i = searchLessOrEqual(keys, l, r, kb);
  99.         }
  100.         final int lowerLeft = l;
  101.         l = i + 1;

  102.         // Find the new right bound for the lower interval using a scan since a
  103.         // typical use case has ka == kb and this is faster than a second binary search.
  104.         while (keys[i] >= ka) {
  105.             --i;
  106.         }
  107.         // return left
  108.         return new KeyUpdatingInterval(keys, lowerLeft, i);
  109.     }

  110.     /**
  111.      * Return the current number of indices in the interval.
  112.      *
  113.      * @return the size
  114.      */
  115.     int size() {
  116.         return r - l + 1;
  117.     }

  118.     /**
  119.      * Search the data for the largest index {@code i} where {@code a[i]} is
  120.      * less-than-or-equal to the {@code key}; else return {@code left - 1}.
  121.      * <pre>
  122.      * a[i] <= k    :   left <= i <= right, or (left - 1)
  123.      * </pre>
  124.      *
  125.      * <p>The data is assumed to be in ascending order, otherwise the behaviour is undefined.
  126.      * If the range contains multiple elements with the {@code key} value, the result index
  127.      * may be any that match.
  128.      *
  129.      * <p>This is similar to using {@link java.util.Arrays#binarySearch(int[], int, int, int)
  130.      * Arrays.binarySearch}. The method differs in:
  131.      * <ul>
  132.      * <li>use of an inclusive upper bound;
  133.      * <li>returning the closest index with a value below {@code key} if no match was not found;
  134.      * <li>performing no range checks: it is assumed {@code left <= right} and they are valid
  135.      * indices into the array.
  136.      * </ul>
  137.      *
  138.      * <p>An equivalent use of binary search is:
  139.      * <pre>{@code
  140.      * int i = Arrays.binarySearch(a, left, right + 1, k);
  141.      * if (i < 0) {
  142.      *     i = ~i - 1;
  143.      * }
  144.      * }</pre>
  145.      *
  146.      * <p>This specialisation avoids the caller checking the binary search result for the use
  147.      * case when the presence or absence of a key is not important; only that the returned
  148.      * index for an absence of a key is the largest index. When used on unique keys this
  149.      * method can be used to update an upper index so all keys are known to be below a key:
  150.      *
  151.      * <pre>{@code
  152.      * int[] keys = ...
  153.      * // [i0, i1] contains all keys
  154.      * int i0 = 0;
  155.      * int i1 = keys.length - 1;
  156.      * // Update: [i0, i1] contains all keys <= k
  157.      * i1 = searchLessOrEqual(keys, i0, i1, k);
  158.      * }</pre>
  159.      *
  160.      * @param a Data.
  161.      * @param left Lower bound (inclusive).
  162.      * @param right Upper bound (inclusive).
  163.      * @param k Key.
  164.      * @return largest index {@code i} such that {@code a[i] <= k}, or {@code left - 1} if no
  165.      * such index exists
  166.      */
  167.     static int searchLessOrEqual(int[] a, int left, int right, int k) {
  168.         int l = left;
  169.         int r = right;
  170.         while (l <= r) {
  171.             // Middle value
  172.             final int m = (l + r) >>> 1;
  173.             final int v = a[m];
  174.             // Test:
  175.             // l------m------r
  176.             //        v  k      update left
  177.             //     k  v         update right
  178.             if (v < k) {
  179.                 l = m + 1;
  180.             } else if (v > k) {
  181.                 r = m - 1;
  182.             } else {
  183.                 // Equal
  184.                 return m;
  185.             }
  186.         }
  187.         // Return largest known value below:
  188.         // r is always moved downward when a middle index value is too high
  189.         return r;
  190.     }
  191. }