View Javadoc
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  
18  package org.apache.commons.numbers.arrays;
19  
20  /**
21   * An {@link UpdatingInterval} backed by an array of ordered keys.
22   *
23   * @since 1.2
24   */
25  final class KeyUpdatingInterval implements UpdatingInterval {
26      /** Size to use a scan of the keys when splitting instead of binary search.
27       * Note binary search has an overhead on small size due to the random left/right
28       * branching per iteration. It is much faster on very large sizes. */
29      private static final int SCAN_SIZE = 256;
30  
31      /** The ordered keys. */
32      private final int[] keys;
33      /** Index of the left key. */
34      private int l;
35      /** Index of the right key. */
36      private int r;
37  
38      /**
39       * Create an instance with the provided {@code indices}.
40       *
41       * <p><strong>Warning:</strong> Indices must be sorted and distinct.
42       *
43       * @param indices Indices.
44       * @param n Number of indices.
45       */
46      KeyUpdatingInterval(int[] indices, int n) {
47          this(indices, 0, n - 1);
48      }
49  
50      /**
51       * @param indices Indices.
52       * @param l Index of left key.
53       * @param r Index of right key.
54       */
55      private KeyUpdatingInterval(int[] indices, int l, int r) {
56          keys = indices;
57          this.l = l;
58          this.r = r;
59      }
60  
61      @Override
62      public int left() {
63          return keys[l];
64      }
65  
66      @Override
67      public int right() {
68          return keys[r];
69      }
70  
71      @Override
72      public int updateLeft(int k) {
73          // Assume left < k <= right (i.e. we must move left at least 1)
74          // Search using a scan on the assumption that k is close to the end
75          int i = l;
76          do {
77              ++i;
78          } while (keys[i] < k);
79          l = i;
80          return keys[i];
81      }
82  
83      @Override
84      public int updateRight(int k) {
85          // Assume left <= k < right (i.e. we must move right at least 1)
86          // Search using a scan on the assumption that k is close to the end
87          int i = r;
88          do {
89              --i;
90          } while (keys[i] > k);
91          r = i;
92          return keys[i];
93      }
94  
95      @Override
96      public UpdatingInterval splitLeft(int ka, int kb) {
97          // left < ka <= kb < right
98  
99          // Find the new left bound for the upper interval.
100         // Switch to a linear scan if length is small.
101         int i;
102         if (r - l < SCAN_SIZE) {
103             i = r;
104             do {
105                 --i;
106             } while (keys[i] > kb);
107         } else {
108             // Binary search
109             i = searchLessOrEqual(keys, l, r, kb);
110         }
111         final int lowerLeft = l;
112         l = i + 1;
113 
114         // Find the new right bound for the lower interval using a scan since a
115         // typical use case has ka == kb and this is faster than a second binary search.
116         while (keys[i] >= ka) {
117             --i;
118         }
119         // return left
120         return new KeyUpdatingInterval(keys, lowerLeft, i);
121     }
122 
123     /**
124      * Return the current number of indices in the interval.
125      *
126      * @return the size
127      */
128     int size() {
129         return r - l + 1;
130     }
131 
132     /**
133      * Search the data for the largest index {@code i} where {@code a[i]} is
134      * less-than-or-equal to the {@code key}; else return {@code left - 1}.
135      * <pre>
136      * a[i] <= k    :   left <= i <= right, or (left - 1)
137      * </pre>
138      *
139      * <p>The data is assumed to be in ascending order, otherwise the behaviour is undefined.
140      * If the range contains multiple elements with the {@code key} value, the result index
141      * may be any that match.
142      *
143      * <p>This is similar to using {@link java.util.Arrays#binarySearch(int[], int, int, int)
144      * Arrays.binarySearch}. The method differs in:
145      * <ul>
146      * <li>use of an inclusive upper bound;
147      * <li>returning the closest index with a value below {@code key} if no match was not found;
148      * <li>performing no range checks: it is assumed {@code left <= right} and they are valid
149      * indices into the array.
150      * </ul>
151      *
152      * <p>An equivalent use of binary search is:
153      * <pre>{@code
154      * int i = Arrays.binarySearch(a, left, right + 1, k);
155      * if (i < 0) {
156      *     i = ~i - 1;
157      * }
158      * }</pre>
159      *
160      * <p>This specialisation avoids the caller checking the binary search result for the use
161      * case when the presence or absence of a key is not important; only that the returned
162      * index for an absence of a key is the largest index. When used on unique keys this
163      * method can be used to update an upper index so all keys are known to be below a key:
164      *
165      * <pre>{@code
166      * int[] keys = ...
167      * // [i0, i1] contains all keys
168      * int i0 = 0;
169      * int i1 = keys.length - 1;
170      * // Update: [i0, i1] contains all keys <= k
171      * i1 = searchLessOrEqual(keys, i0, i1, k);
172      * }</pre>
173      *
174      * @param a Data.
175      * @param left Lower bound (inclusive).
176      * @param right Upper bound (inclusive).
177      * @param k Key.
178      * @return largest index {@code i} such that {@code a[i] <= k}, or {@code left - 1} if no
179      * such index exists
180      */
181     static int searchLessOrEqual(int[] a, int left, int right, int k) {
182         int l = left;
183         int r = right;
184         while (l <= r) {
185             // Middle value
186             final int m = (l + r) >>> 1;
187             final int v = a[m];
188             // Test:
189             // l------m------r
190             //        v  k      update left
191             //     k  v         update right
192             if (v < k) {
193                 l = m + 1;
194             } else if (v > k) {
195                 r = m - 1;
196             } else {
197                 // Equal
198                 return m;
199             }
200         }
201         // Return largest known value below:
202         // r is always moved downward when a middle index value is too high
203         return r;
204     }
205 }