summaryrefslogtreecommitdiff
path: root/src/main/java/org/apache/commons/math3/util/IntegerSequence.java
blob: 7859ec3271159acb7c9c51c47ffd00aafde4554c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
/*
 * 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.commons.math3.util;

import org.apache.commons.math3.exception.MathUnsupportedOperationException;
import org.apache.commons.math3.exception.MaxCountExceededException;
import org.apache.commons.math3.exception.NotStrictlyPositiveException;
import org.apache.commons.math3.exception.NullArgumentException;
import org.apache.commons.math3.exception.ZeroException;

import java.util.Iterator;

/**
 * Provides a sequence of integers.
 *
 * @since 3.6
 */
public class IntegerSequence {
    /** Utility class contains only static methods. */
    private IntegerSequence() {}

    /**
     * Creates a sequence {@code [start .. end]}. It calls {@link #range(int,int,int) range(start,
     * end, 1)}.
     *
     * @param start First value of the range.
     * @param end Last value of the range.
     * @return a range.
     */
    public static Range range(int start, int end) {
        return range(start, end, 1);
    }

    /**
     * Creates a sequence \( a_i, i < 0 <= n \) where \( a_i = start + i * step \) and \( n \) is
     * such that \( a_n <= max \) and \( a_{n+1} > max \).
     *
     * @param start First value of the range.
     * @param max Last value of the range that satisfies the above construction rule.
     * @param step Increment.
     * @return a range.
     */
    public static Range range(final int start, final int max, final int step) {
        return new Range(start, max, step);
    }

    /** Generates a sequence of integers. */
    public static class Range implements Iterable<Integer> {
        /** Number of integers contained in this range. */
        private final int size;

        /** First value. */
        private final int start;

        /** Final value. */
        private final int max;

        /** Increment. */
        private final int step;

        /**
         * Creates a sequence \( a_i, i < 0 <= n \) where \( a_i = start + i * step \) and \( n \)
         * is such that \( a_n <= max \) and \( a_{n+1} > max \).
         *
         * @param start First value of the range.
         * @param max Last value of the range that satisfies the above construction rule.
         * @param step Increment.
         */
        public Range(int start, int max, int step) {
            this.start = start;
            this.max = max;
            this.step = step;

            final int s = (max - start) / step + 1;
            this.size = s < 0 ? 0 : s;
        }

        /**
         * Gets the number of elements contained in the range.
         *
         * @return the size of the range.
         */
        public int size() {
            return size;
        }

        /** {@inheritDoc} */
        public Iterator<Integer> iterator() {
            return Incrementor.create()
                    .withStart(start)
                    .withMaximalCount(max + (step > 0 ? 1 : -1))
                    .withIncrement(step);
        }
    }

    /**
     * Utility that increments a counter until a maximum is reached, at which point, the instance
     * will by default throw a {@link MaxCountExceededException}. However, the user is able to
     * override this behaviour by defining a custom {@link MaxCountExceededCallback callback}, in
     * order to e.g. select which exception must be thrown.
     */
    public static class Incrementor implements Iterator<Integer> {
        /** Default callback. */
        private static final MaxCountExceededCallback CALLBACK =
                new MaxCountExceededCallback() {
                    /** {@inheritDoc} */
                    public void trigger(int max) throws MaxCountExceededException {
                        throw new MaxCountExceededException(max);
                    }
                };

        /** Initial value the counter. */
        private final int init;

        /** Upper limit for the counter. */
        private final int maximalCount;

        /** Increment. */
        private final int increment;

        /** Function called at counter exhaustion. */
        private final MaxCountExceededCallback maxCountCallback;

        /** Current count. */
        private int count = 0;

        /**
         * Defines a method to be called at counter exhaustion. The {@link #trigger(int) trigger}
         * method should usually throw an exception.
         */
        public interface MaxCountExceededCallback {
            /**
             * Function called when the maximal count has been reached.
             *
             * @param maximalCount Maximal count.
             * @throws MaxCountExceededException at counter exhaustion
             */
            void trigger(int maximalCount) throws MaxCountExceededException;
        }

        /**
         * Creates an incrementor. The counter will be exhausted either when {@code max} is reached
         * or when {@code nTimes} increments have been performed.
         *
         * @param start Initial value.
         * @param max Maximal count.
         * @param step Increment.
         * @param cb Function to be called when the maximal count has been reached.
         * @throws NullArgumentException if {@code cb} is {@code null}.
         */
        private Incrementor(int start, int max, int step, MaxCountExceededCallback cb)
                throws NullArgumentException {
            if (cb == null) {
                throw new NullArgumentException();
            }
            this.init = start;
            this.maximalCount = max;
            this.increment = step;
            this.maxCountCallback = cb;
            this.count = start;
        }

        /**
         * Factory method that creates a default instance. The initial and maximal values are set to
         * 0. For the new instance to be useful, the maximal count must be set by calling {@link
         * #withMaximalCount(int) withMaximalCount}.
         *
         * @return an new instance.
         */
        public static Incrementor create() {
            return new Incrementor(0, 0, 1, CALLBACK);
        }

        /**
         * Creates a new instance with a given initial value. The counter is reset to the initial
         * value.
         *
         * @param start Initial value of the counter.
         * @return a new instance.
         */
        public Incrementor withStart(int start) {
            return new Incrementor(start, this.maximalCount, this.increment, this.maxCountCallback);
        }

        /**
         * Creates a new instance with a given maximal count. The counter is reset to the initial
         * value.
         *
         * @param max Maximal count.
         * @return a new instance.
         */
        public Incrementor withMaximalCount(int max) {
            return new Incrementor(this.init, max, this.increment, this.maxCountCallback);
        }

        /**
         * Creates a new instance with a given increment. The counter is reset to the initial value.
         *
         * @param step Increment.
         * @return a new instance.
         */
        public Incrementor withIncrement(int step) {
            if (step == 0) {
                throw new ZeroException();
            }
            return new Incrementor(this.init, this.maximalCount, step, this.maxCountCallback);
        }

        /**
         * Creates a new instance with a given callback. The counter is reset to the initial value.
         *
         * @param cb Callback to be called at counter exhaustion.
         * @return a new instance.
         */
        public Incrementor withCallback(MaxCountExceededCallback cb) {
            return new Incrementor(this.init, this.maximalCount, this.increment, cb);
        }

        /**
         * Gets the upper limit of the counter.
         *
         * @return the counter upper limit.
         */
        public int getMaximalCount() {
            return maximalCount;
        }

        /**
         * Gets the current count.
         *
         * @return the current count.
         */
        public int getCount() {
            return count;
        }

        /**
         * Checks whether incrementing the counter {@code nTimes} is allowed.
         *
         * @return {@code false} if calling {@link #increment()} will trigger a {@code
         *     MaxCountExceededException}, {@code true} otherwise.
         */
        public boolean canIncrement() {
            return canIncrement(1);
        }

        /**
         * Checks whether incrementing the counter several times is allowed.
         *
         * @param nTimes Number of increments.
         * @return {@code false} if calling {@link #increment(int) increment(nTimes)} would call the
         *     {@link MaxCountExceededCallback callback} {@code true} otherwise.
         */
        public boolean canIncrement(int nTimes) {
            final int finalCount = count + nTimes * increment;
            return increment < 0 ? finalCount > maximalCount : finalCount < maximalCount;
        }

        /**
         * Performs multiple increments.
         *
         * @param nTimes Number of increments.
         * @throws MaxCountExceededException at counter exhaustion.
         * @throws NotStrictlyPositiveException if {@code nTimes <= 0}.
         * @see #increment()
         */
        public void increment(int nTimes) throws MaxCountExceededException {
            if (nTimes <= 0) {
                throw new NotStrictlyPositiveException(nTimes);
            }

            if (!canIncrement(0)) {
                maxCountCallback.trigger(maximalCount);
            }
            count += nTimes * increment;
        }

        /**
         * Adds the increment value to the current iteration count. At counter exhaustion, this
         * method will call the {@link MaxCountExceededCallback#trigger(int) trigger} method of the
         * callback object passed to the {@link #withCallback(MaxCountExceededCallback)} method. If
         * not explicitly set, a default callback is used that will throw a {@code
         * MaxCountExceededException}.
         *
         * @throws MaxCountExceededException at counter exhaustion, unless a custom {@link
         *     MaxCountExceededCallback callback} has been set.
         * @see #increment(int)
         */
        public void increment() throws MaxCountExceededException {
            increment(1);
        }

        /** {@inheritDoc} */
        public boolean hasNext() {
            return canIncrement(0);
        }

        /** {@inheritDoc} */
        public Integer next() {
            final int value = count;
            increment();
            return value;
        }

        /**
         * Not applicable.
         *
         * @throws MathUnsupportedOperationException
         */
        public void remove() {
            throw new MathUnsupportedOperationException();
        }
    }
}