blob: 692ce4619238fd6dec045bdf0b71c948babfc04d [file] [log] [blame]
/*
* 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.math4.legacy.core;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.apache.commons.math4.legacy.exception.MaxCountExceededException;
import org.apache.commons.math4.legacy.exception.NullArgumentException;
import org.apache.commons.math4.legacy.exception.MathUnsupportedOperationException;
import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
import org.apache.commons.math4.legacy.exception.ZeroException;
/**
* Provides a sequence of integers.
*
* @since 3.6
*/
public final 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 <code>a<sub>i</sub>, i &lt; 0 &lt; n</code>
* where <code>a<sub>i</sub> = start + i * step</code>
* and {@code n} is such that <code>a<sub>n</sub> &lt;= max</code>
* and <code>a<sub>n+1</sub> &gt; max</code>.
*
* @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 <code>a<sub>i</sub>, i &lt; 0 &lt; n</code>
* where <code>a<sub>i</sub> = start + i * step</code>
* and {@code n} is such that <code>a<sub>n</sub> &lt;= max</code>
* and <code>a<sub>n+1</sub> &gt; max</code>.
*
* @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} */
@Override
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 final class Incrementor implements Iterator<Integer> {
/** Default callback. */
private static final MaxCountExceededCallback CALLBACK
= new MaxCountExceededCallback() {
/** {@inheritDoc} */
@Override
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;
/**
* 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);
}
count += nTimes * increment;
if (!canIncrement(0)) {
maxCountCallback.trigger(maximalCount);
}
}
/**
* 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} */
@Override
public boolean hasNext() {
return canIncrement(0);
}
/** {@inheritDoc} */
@Override
public Integer next() {
if (canIncrement(0)) {
final int value = count;
count += increment;
return value;
} else {
// Contract for "Iterator".
throw new NoSuchElementException();
}
}
/**
* Not applicable.
*
* @throws MathUnsupportedOperationException always
*/
@Override
public void remove() {
throw new MathUnsupportedOperationException();
}
}
}