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  package org.apache.commons.math4.legacy.core;
18  
19  import java.util.Iterator;
20  import java.util.NoSuchElementException;
21  import org.apache.commons.math4.legacy.exception.MaxCountExceededException;
22  import org.apache.commons.math4.legacy.exception.NullArgumentException;
23  import org.apache.commons.math4.legacy.exception.MathUnsupportedOperationException;
24  import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
25  import org.apache.commons.math4.legacy.exception.ZeroException;
26  
27  /**
28   * Provides a sequence of integers.
29   *
30   * @since 3.6
31   */
32  public final class IntegerSequence {
33      /**
34       * Utility class contains only static methods.
35       */
36      private IntegerSequence() {}
37  
38      /**
39       * Creates a sequence {@code [start .. end]}.
40       * It calls {@link #range(int,int,int) range(start, end, 1)}.
41       *
42       * @param start First value of the range.
43       * @param end Last value of the range.
44       * @return a range.
45       */
46      public static Range range(int start,
47                                int end) {
48          return range(start, end, 1);
49      }
50  
51      /**
52       * Creates a sequence <code>a<sub>i</sub>, i &lt; 0 &lt; n</code>
53       * where <code>a<sub>i</sub> = start + i * step</code>
54       * and {@code n} is such that <code>a<sub>n</sub> &lt;= max</code>
55       * and  <code>a<sub>n+1</sub> &gt; max</code>.
56       *
57       * @param start First value of the range.
58       * @param max Last value of the range that satisfies the above
59       * construction rule.
60       * @param step Increment.
61       * @return a range.
62       */
63      public static Range range(final int start,
64                                final int max,
65                                final int step) {
66          return new Range(start, max, step);
67      }
68  
69      /**
70       * Generates a sequence of integers.
71       */
72      public static class Range implements Iterable<Integer> {
73          /** Number of integers contained in this range. */
74          private final int size;
75          /** First value. */
76          private final int start;
77          /** Final value. */
78          private final int max;
79          /** Increment. */
80          private final int step;
81  
82          /**
83           * Creates a sequence <code>a<sub>i</sub>, i &lt; 0 &lt; n</code>
84           * where <code>a<sub>i</sub> = start + i * step</code>
85           * and {@code n} is such that <code>a<sub>n</sub> &lt;= max</code>
86           * and  <code>a<sub>n+1</sub> &gt; max</code>.
87           *
88           * @param start First value of the range.
89           * @param max Last value of the range that satisfies the above
90           * construction rule.
91           * @param step Increment.
92           */
93          public Range(int start,
94                       int max,
95                       int step) {
96              this.start = start;
97              this.max = max;
98              this.step = step;
99  
100             final int s = (max - start) / step + 1;
101             this.size = s < 0 ? 0 : s;
102         }
103 
104         /**
105          * Gets the number of elements contained in the range.
106          *
107          * @return the size of the range.
108          */
109         public int size() {
110             return size;
111         }
112 
113         /** {@inheritDoc} */
114         @Override
115         public Iterator<Integer> iterator() {
116             return Incrementor.create()
117                 .withStart(start)
118                 .withMaximalCount(max + (step > 0 ? 1 : -1))
119                 .withIncrement(step);
120         }
121     }
122 
123     /**
124      * Utility that increments a counter until a maximum is reached, at
125      * which point, the instance will by default throw a
126      * {@link MaxCountExceededException}.
127      * However, the user is able to override this behaviour by defining a
128      * custom {@link MaxCountExceededCallback callback}, in order to e.g.
129      * select which exception must be thrown.
130      */
131     public static final class Incrementor implements Iterator<Integer> {
132         /** Default callback. */
133         private static final MaxCountExceededCallback CALLBACK
134             = new MaxCountExceededCallback() {
135                     /** {@inheritDoc} */
136                     @Override
137                     public void trigger(int max) throws MaxCountExceededException {
138                         throw new MaxCountExceededException(max);
139                     }
140                 };
141 
142         /** Initial value the counter. */
143         private final int init;
144         /** Upper limit for the counter. */
145         private final int maximalCount;
146         /** Increment. */
147         private final int increment;
148         /** Function called at counter exhaustion. */
149         private final MaxCountExceededCallback maxCountCallback;
150         /** Current count. */
151         private int count;
152 
153         /**
154          * Defines a method to be called at counter exhaustion.
155          * The {@link #trigger(int) trigger} method should usually throw an exception.
156          */
157         public interface MaxCountExceededCallback {
158             /**
159              * Function called when the maximal count has been reached.
160              *
161              * @param maximalCount Maximal count.
162              * @throws MaxCountExceededException at counter exhaustion
163              */
164             void trigger(int maximalCount) throws MaxCountExceededException;
165         }
166 
167         /**
168          * Creates an incrementor.
169          * The counter will be exhausted either when {@code max} is reached
170          * or when {@code nTimes} increments have been performed.
171          *
172          * @param start Initial value.
173          * @param max Maximal count.
174          * @param step Increment.
175          * @param cb Function to be called when the maximal count has been reached.
176          * @throws NullArgumentException if {@code cb} is {@code null}.
177          */
178         private Incrementor(int start,
179                             int max,
180                             int step,
181                             MaxCountExceededCallback cb)
182             throws NullArgumentException {
183             if (cb == null) {
184                 throw new NullArgumentException();
185             }
186             this.init = start;
187             this.maximalCount = max;
188             this.increment = step;
189             this.maxCountCallback = cb;
190             this.count = start;
191         }
192 
193         /**
194          * Factory method that creates a default instance.
195          * The initial and maximal values are set to 0.
196          * For the new instance to be useful, the maximal count must be set
197          * by calling {@link #withMaximalCount(int) withMaximalCount}.
198          *
199          * @return an new instance.
200          */
201         public static Incrementor create() {
202             return new Incrementor(0, 0, 1, CALLBACK);
203         }
204 
205         /**
206          * Creates a new instance with a given initial value.
207          * The counter is reset to the initial value.
208          *
209          * @param start Initial value of the counter.
210          * @return a new instance.
211          */
212         public Incrementor withStart(int start) {
213             return new Incrementor(start,
214                                    this.maximalCount,
215                                    this.increment,
216                                    this.maxCountCallback);
217         }
218 
219         /**
220          * Creates a new instance with a given maximal count.
221          * The counter is reset to the initial value.
222          *
223          * @param max Maximal count.
224          * @return a new instance.
225          */
226         public Incrementor withMaximalCount(int max) {
227             return new Incrementor(this.init,
228                                    max,
229                                    this.increment,
230                                    this.maxCountCallback);
231         }
232 
233         /**
234          * Creates a new instance with a given increment.
235          * The counter is reset to the initial value.
236          *
237          * @param step Increment.
238          * @return a new instance.
239          */
240         public Incrementor withIncrement(int step) {
241             if (step == 0) {
242                 throw new ZeroException();
243             }
244             return new Incrementor(this.init,
245                                    this.maximalCount,
246                                    step,
247                                    this.maxCountCallback);
248         }
249 
250         /**
251          * Creates a new instance with a given callback.
252          * The counter is reset to the initial value.
253          *
254          * @param cb Callback to be called at counter exhaustion.
255          * @return a new instance.
256          */
257         public Incrementor withCallback(MaxCountExceededCallback cb) {
258             return new Incrementor(this.init,
259                                    this.maximalCount,
260                                    this.increment,
261                                    cb);
262         }
263 
264         /**
265          * Gets the upper limit of the counter.
266          *
267          * @return the counter upper limit.
268          */
269         public int getMaximalCount() {
270             return maximalCount;
271         }
272 
273         /**
274          * Gets the current count.
275          *
276          * @return the current count.
277          */
278         public int getCount() {
279             return count;
280         }
281 
282         /**
283          * Checks whether incrementing the counter {@code nTimes} is allowed.
284          *
285          * @return {@code false} if calling {@link #increment()}
286          * will trigger a {@code MaxCountExceededException},
287          * {@code true} otherwise.
288          */
289         public boolean canIncrement() {
290             return canIncrement(1);
291         }
292 
293         /**
294          * Checks whether incrementing the counter several times is allowed.
295          *
296          * @param nTimes Number of increments.
297          * @return {@code false} if calling {@link #increment(int)
298          * increment(nTimes)} would call the {@link MaxCountExceededCallback callback}
299          * {@code true} otherwise.
300          */
301         public boolean canIncrement(int nTimes) {
302             final int finalCount = count + nTimes * increment;
303             return increment < 0 ?
304                 finalCount > maximalCount :
305                 finalCount < maximalCount;
306         }
307 
308         /**
309          * Performs multiple increments.
310          *
311          * @param nTimes Number of increments.
312          * @throws MaxCountExceededException at counter exhaustion.
313          * @throws NotStrictlyPositiveException if {@code nTimes <= 0}.
314          *
315          * @see #increment()
316          */
317         public void increment(int nTimes) throws MaxCountExceededException {
318             if (nTimes <= 0) {
319                 throw new NotStrictlyPositiveException(nTimes);
320             }
321 
322             count += nTimes * increment;
323 
324             if (!canIncrement(0)) {
325                 maxCountCallback.trigger(maximalCount);
326             }
327         }
328 
329         /**
330          * Adds the increment value to the current iteration count.
331          * At counter exhaustion, this method will call the
332          * {@link MaxCountExceededCallback#trigger(int) trigger} method of the
333          * callback object passed to the
334          * {@link #withCallback(MaxCountExceededCallback)} method.
335          * If not explicitly set, a default callback is used that will throw
336          * a {@code MaxCountExceededException}.
337          *
338          * @throws MaxCountExceededException at counter exhaustion, unless a
339          * custom {@link MaxCountExceededCallback callback} has been set.
340          *
341          * @see #increment(int)
342          */
343         public void increment() throws MaxCountExceededException {
344             increment(1);
345         }
346 
347         /** {@inheritDoc} */
348         @Override
349         public boolean hasNext() {
350             return canIncrement(0);
351         }
352 
353         /** {@inheritDoc} */
354         @Override
355         public Integer next() {
356             if (canIncrement(0)) {
357                 final int value = count;
358                 count += increment;
359                 return value;
360             } else {
361                 // Contract for "Iterator".
362                 throw new NoSuchElementException();
363             }
364         }
365 
366         /**
367          * Not applicable.
368          *
369          * @throws MathUnsupportedOperationException always
370          */
371         @Override
372         public void remove() {
373             throw new MathUnsupportedOperationException();
374         }
375     }
376 }