MATH-1259

New class to replace the current "Incrementor" (now deprecated).
Additional functionality: negative initial value and/or increment,
"range" utility method.
This commit is contained in:
Gilles 2015-08-30 16:23:23 +02:00
parent 7b9df59a96
commit 818533e92b
3 changed files with 575 additions and 0 deletions

View File

@ -28,7 +28,10 @@ import org.apache.commons.math4.exception.NullArgumentException;
* select which exception must be thrown.
*
* @since 3.0
*
* @deprecated Use {@link IntegerSequence.Incrementor} instead.
*/
@Deprecated
public class Incrementor {
/**
* Upper limit for the counter.

View File

@ -0,0 +1,324 @@
/*
* 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.util;
import java.util.Iterator;
import org.apache.commons.math4.exception.MaxCountExceededException;
import org.apache.commons.math4.exception.NullArgumentException;
import org.apache.commons.math4.exception.MathUnsupportedOperationException;
import org.apache.commons.math4.exception.NotStrictlyPositiveException;
import org.apache.commons.math4.exception.ZeroException;
/**
* 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 Iterable<Integer> 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 Iterable<Integer> range(final int start,
final int max,
final int step) {
return new Iterable<Integer>() {
/** {@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 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 = 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 nTimes Number of increments.
* @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 explictly 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() {
final int value = count;
increment();
return value;
}
/**
* Not applicable.
*
* @throws MathUnsupportedOperationException
*/
@Override
public void remove() {
throw new MathUnsupportedOperationException();
}
}
}

View File

@ -0,0 +1,248 @@
/*
* 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.util;
import java.util.List;
import java.util.ArrayList;
import org.apache.commons.math4.exception.MaxCountExceededException;
import org.apache.commons.math4.exception.TooManyEvaluationsException;
import org.apache.commons.math4.exception.NotStrictlyPositiveException;
import org.apache.commons.math4.exception.ZeroException;
import org.junit.Assert;
import org.junit.Test;
/**
* Tests for {@link IntegerSequence} and {@link IntegerSequence#Incrementor}.
*/
public class IntegerSequenceTest {
@Test
public void testIncreasingRange() {
final int start = 1;
final int max = 7;
final int step = 2;
final List<Integer> seq = new ArrayList<Integer>();
for (Integer i : IntegerSequence.range(start, max, step)) {
seq.add(i);
}
Assert.assertEquals(4, seq.size());
for (int i = 0; i < seq.size(); i++) {
Assert.assertEquals(start + i * step, seq.get(i).intValue());
}
}
@Test
public void testIncreasingRangeNegativeEnd() {
final int start = -10;
final int max = -1;
final int step = 2;
final List<Integer> seq = new ArrayList<Integer>();
for (Integer i : IntegerSequence.range(start, max, step)) {
seq.add(i);
}
Assert.assertEquals(5, seq.size());
for (int i = 0; i < seq.size(); i++) {
Assert.assertEquals(start + i * step, seq.get(i).intValue());
}
}
@Test
public void testDecreasingRange() {
final int start = 10;
final int max = -8;
final int step = -3;
final List<Integer> seq = new ArrayList<Integer>();
for (Integer i : IntegerSequence.range(start, max, step)) {
seq.add(i);
}
Assert.assertEquals(7, seq.size());
for (int i = 0; i < seq.size(); i++) {
Assert.assertEquals(start + i * step, seq.get(i).intValue());
}
}
@Test
public void testSingleElementRange() {
final int start = 1;
final int max = 1;
final int step = -1;
final List<Integer> seq = new ArrayList<Integer>();
for (Integer i : IntegerSequence.range(start, max, step)) {
seq.add(i);
}
Assert.assertEquals(1, seq.size());
Assert.assertEquals(start, seq.get(0).intValue());
}
@Test
public void testBasicRange() {
final int start = -2;
final int end = 4;
final List<Integer> seq = new ArrayList<Integer>();
for (Integer i : IntegerSequence.range(start, end)) {
seq.add(i);
}
for (int i = start; i <= end; i++) {
Assert.assertEquals(i, seq.get(i - start).intValue());
}
}
@Test
public void testEmptyRange() {
final int start = 2;
final int end = 1;
final List<Integer> seq = new ArrayList<Integer>();
for (Integer i : IntegerSequence.range(start, end)) {
seq.add(i);
}
Assert.assertEquals(0, seq.size());
}
@Test
public void testEmptyRangeNegativeStart() {
final int start = -2;
final int max = -1;
final int step = -1;
final List<Integer> seq = new ArrayList<Integer>();
for (Integer i : IntegerSequence.range(start, max, step)) {
seq.add(i);
}
Assert.assertEquals(0, seq.size());
}
@Test(expected=MaxCountExceededException.class)
public void testIncrementorCountExceeded() {
final int start = 1;
final int max = 7;
final int step = 2;
final IntegerSequence.Incrementor inc =
IntegerSequence.Incrementor.create()
.withStart(start)
.withMaximalCount(max)
.withIncrement(step);
Assert.assertTrue(inc.canIncrement(2));
Assert.assertFalse(inc.canIncrement(3));
while (true) {
inc.increment();
}
}
@Test
public void testCanIncrementZeroTimes() {
final int start = 1;
final int max = 2;
final int step = 1;
final IntegerSequence.Incrementor inc
= IntegerSequence.Incrementor.create()
.withStart(start)
.withMaximalCount(max)
.withIncrement(step);
Assert.assertTrue(inc.canIncrement(0));
}
@Test(expected=NotStrictlyPositiveException.class)
public void testIncrementZeroTimes() {
final int start = 1;
final int max = 2;
final int step = 1;
final IntegerSequence.Incrementor inc
= IntegerSequence.Incrementor.create()
.withStart(start)
.withMaximalCount(max)
.withIncrement(step);
inc.increment(0);
}
@Test(expected=ZeroException.class)
public void testIncrementZeroStep() {
final int step = 0;
final IntegerSequence.Incrementor inc
= IntegerSequence.Incrementor.create()
.withIncrement(step);
}
@Test
public void testIteratorZeroElement() {
final int start = 1;
final int max = 1;
final int step = 1;
final IntegerSequence.Incrementor inc
= IntegerSequence.Incrementor.create()
.withStart(start)
.withMaximalCount(max)
.withIncrement(step);
Assert.assertFalse(inc.hasNext());
try {
inc.increment();
Assert.fail("exception expected");
} catch (MaxCountExceededException e) {
// Expected.
}
}
@Test(expected=TooManyEvaluationsException.class)
public void testIncrementorAlternateException() {
final int start = 1;
final int max = 2;
final int step = 1;
final IntegerSequence.Incrementor.MaxCountExceededCallback cb
= new IntegerSequence.Incrementor.MaxCountExceededCallback() {
/** {@inheritDoc} */
public void trigger(int max) {
throw new TooManyEvaluationsException(max);
}
};
final IntegerSequence.Incrementor inc
= IntegerSequence.Incrementor.create()
.withStart(start)
.withMaximalCount(max)
.withIncrement(step)
.withCallback(cb);
try {
// One call must succeed.
inc.increment();
} catch (RuntimeException e) {
Assert.fail("unexpected exception");
}
// Second call must fail.
inc.increment();
}
}