Introduction
After getting stuck with a Pythonish integer range in C++, I rewrote the facility in Java. In Python you can say:
>>> range(38, 0, -3) [38, 35, 32, 29, 26, 23, 20, 17, 14, 11, 8, 5, 2]
In Java you can say:
for (int i : range(10)) { ... }
or, if need be:
List<Integer> list = new ArrayList<>(range(100, -100, -7));
Implementation
Range.java:
package net.coderodde.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
/**
* This class implements an iterator over an arithmetic progression of integers.
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Dec 1, 2015)
*/
public class Range implements Iterable<Integer>, Collection<Integer> {
private final int start;
private final int step;
private final int end;
public static Range range(int start, int end, int step) {
return new Range(start, end, step);
}
public static Range range(int start, int end) {
return new Range(start, end);
}
public static Range range(int end) {
return new Range(end);
}
public Range(int start, int end, int step) {
if (step == 0) {
throw new IllegalArgumentException("The step is zero.");
}
this.start = start;
this.step = step;
this.end = end;
}
public Range(int start, int end) {
this(start, end, 1);
}
public Range(int end) {
this(0, end);
}
@Override
public Iterator<Integer> iterator() {
if (start <= end) {
return new AscendingRangeIterator(start,
step,
(step < 0 ? start : end));
} else {
return new DescendingRangeIterator(start,
step,
(step > 0 ? start : end));
}
}
@Override
public int size() {
if (start <= end) {
if (step < 0) {
return 0;
}
int rangeLength = end - start;
return rangeLength / step + (rangeLength % step != 0 ? 1 : 0);
} else {
if (step > 0) {
return 0;
}
int rangeLength = start - end;
return rangeLength / -step + (rangeLength % -step != 0 ? 1 : 0);
}
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public boolean contains(Object o) {
if (!(o instanceof Integer)) {
return false;
}
if (start <= end) {
if (step < 0) {
return false;
}
Integer other = (Integer) o;
if (other < start || other >= end) {
return false;
}
return (other - start) % step == 0;
} else {
if (step > 0) {
return false;
}
Integer other = (Integer) o;
if (other > start || other <= end) {
return false;
}
return (start - other) % -step == 0;
}
}
@Override
public Object[] toArray() {
Object[] array = new Object[size()];
// We could have iterated using the iterator, yet we optimize this a bit
if (start <= end) {
if (step > 0) {
for (int j = 0, i = start; i < end; i += step, ++j) {
array[j] = i;
}
}
} else {
if (step < 0) {
for (int j = 0, i = start; i > end; i += step, ++j) {
array[j] = i;
}
}
}
return array;
}
@SuppressWarnings("unchecked")
@Override
public <T> T[] toArray(T[] a) {
T[] ret;
int size = size();
if (a.length < size) {
ret = Arrays.copyOf(a, size);
} else {
ret = a;
}
if (start <= end) {
if (step > 0) {
for (int i = start, j = 0; i < end; i += step, ++j) {
ret[j] = (T) Integer.valueOf(i);
}
}
} else {
if (step < 0) {
for (int i = start, j = 0; i > end; i += step, ++j) {
ret[j] = (T) Integer.valueOf(i);
}
}
}
if (size < ret.length) {
ret[size] = null;
}
return ret;
}
@Override
public boolean add(Integer e) {
throw new UnsupportedOperationException(
"Adding to Range is not supported.");
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException(
"Removing from Range is not supported.");
}
@Override
public boolean containsAll(Collection<?> c) {
for (Object o : c) {
if (!contains(o)) {
return false;
}
}
return true;
}
@Override
public boolean addAll(Collection<? extends Integer> c) {
throw new UnsupportedOperationException(
"Adding to Range is not supported.");
}
@Override
public boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException(
"Removing from Range is not supported.");
}
@Override
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException(
"Retaining in Range is not supported.");
}
@Override
public void clear() {
throw new UnsupportedOperationException(
"Clearing a Range is not supported.");
}
private final class AscendingRangeIterator implements Iterator<Integer> {
private int value;
private final int step;
private final int boundValue;
AscendingRangeIterator(int value, int step, int boundValue) {
this.value = value;
this.step = step;
this.boundValue = boundValue;
}
@Override
public boolean hasNext() {
return value < boundValue;
}
@Override
public Integer next() {
if (!hasNext()) {
throw new NoSuchElementException("Iteration exceeded.");
}
int ret = value;
value += step;
return ret;
}
}
private final class DescendingRangeIterator implements Iterator<Integer> {
private int value;
private final int step;
private final int boundValue;
DescendingRangeIterator(int value, int step, int boundValue) {
this.value = value;
this.step = Math.abs(step);
this.boundValue = boundValue;
}
@Override
public boolean hasNext() {
return value > boundValue;
}
@Override
public Integer next() {
if (!hasNext()) {
throw new NoSuchElementException("Iteration exceeded.");
}
int ret = value;
value -= step;
return ret;
}
}
public static void main(String[] args) {
Range r = range(100, 10, -3);
List<Integer> list = new ArrayList<>(r);
int j = 0;
for (int i : r) {
System.out.printf("%3d : %3d\n", i, list.get(j++));
}
}
}
RangeTest.java:
package net.coderodde.util;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import static org.junit.Assert.*;
import static net.coderodde.util.Range.*;
public class RangeTest {
@Test
public void testRange_3args() {
/////////////////////////////////////////////////////////
List<Integer> list = new ArrayList<>(range(37, -11, -5));
/////////////////////////////////////////////////////////
assertEquals(10, list.size());
for (int i = 37, j = 0; i > -11; i -= 5, ++j) {
assertEquals(Integer.valueOf(i), list.get(j));
}
Range r = range(10, 50, 7);
assertTrue(r.contains(10));
assertTrue(r.contains(17));
assertTrue(r.contains(24));
assertTrue(r.contains(31));
assertTrue(r.contains(38));
assertTrue(r.contains(45));
assertEquals(6, r.size());
Iterator<Integer> iterator = r.iterator();
for (int i = 10; i < 50; i += 7) {
assertTrue(iterator.hasNext());
assertEquals(Integer.valueOf(i), iterator.next());
}
assertFalse(iterator.hasNext());
for (int i = -100; i < 10; ++i) {
assertFalse(r.contains(i));
}
for (int i = 50; i < 100; ++i) {
assertFalse(r.contains(i));
}
////
for (int step = -1; step > -10; --step) {
r = range(5, 50, step);
assertEquals(0, r.size());
assertFalse(r.iterator().hasNext());
}
for (int step = 1; step < 10; ++step) {
r = range(50, 5, step);
assertEquals(0, r.size());
assertFalse(r.iterator().hasNext());
}
}
@Test
public void testRange_int_int() {
Range r = range(10, 17);
for (int i = 10; i < 17; ++i) {
assertTrue(r.contains(i));
}
assertEquals(7, r.size());
Iterator<Integer> iterator = r.iterator();
for (int i = 10; i < 17; ++i) {
assertTrue(iterator.hasNext());
assertEquals(Integer.valueOf(i), iterator.next());
}
assertFalse(iterator.hasNext());
for (int i = -100; i < 10; ++i) {
assertFalse(r.contains(i));
}
for (int i = 17; i < 100; ++i) {
assertFalse(r.contains(i));
}
}
@Test
public void testRange_int() {
Range r = range(5);
for (int i = 0; i < 5; ++i) {
assertTrue(r.contains(i));
}
assertEquals(5, r.size());
Iterator<Integer> iterator = r.iterator();
for (int i = 0; i < 5; ++i) {
assertTrue(iterator.hasNext());
assertEquals(Integer.valueOf(i), iterator.next());
}
assertFalse(iterator.hasNext());
}
@Test
public void testIterator() {
Range r = range(50, 1, -3);
assertEquals(17, r.size());
Iterator<Integer> iterator = r.iterator();
assertEquals(Integer.valueOf(50), iterator.next());
assertEquals(Integer.valueOf(47), iterator.next());
assertEquals(Integer.valueOf(44), iterator.next());
assertEquals(Integer.valueOf(41), iterator.next());
assertEquals(Integer.valueOf(38), iterator.next());
assertEquals(Integer.valueOf(35), iterator.next());
assertEquals(Integer.valueOf(32), iterator.next());
assertEquals(Integer.valueOf(29), iterator.next());
assertEquals(Integer.valueOf(26), iterator.next());
assertEquals(Integer.valueOf(23), iterator.next());
assertEquals(Integer.valueOf(20), iterator.next());
assertEquals(Integer.valueOf(17), iterator.next());
assertEquals(Integer.valueOf(14), iterator.next());
assertEquals(Integer.valueOf(11), iterator.next());
assertEquals(Integer.valueOf(8), iterator.next());
assertEquals(Integer.valueOf(5), iterator.next());
assertEquals(Integer.valueOf(2), iterator.next());
assertFalse(iterator.hasNext());
}
@Test
public void testSize() {
Range r = range(10, 2, -2);
assertEquals(4, r.size());
r = range(2, 10, 2);
assertEquals(4, r.size());
r = range(10, 2, 2);
assertEquals(0, r.size());
r = range(2, 10, -2);
assertEquals(0, r.size());
}
@Test
public void testIsEmpty() {
Range r = range(0);
assertTrue(r.isEmpty());
r = range(20, 33, 3);
assertFalse(r.isEmpty());
r = range(20, 33, -3);
assertTrue(r.isEmpty());
r = range(33, 20, -3);
assertFalse(r.isEmpty());
r = range(33, 20, 3);
assertTrue(r.isEmpty());
}
@Test
public void testContains() {
Range r = range(10, 0, 2);
for (int i = 0; i <= 10; i += 2) {
assertFalse(r.contains(i));
}
r = range(10, 1, -2);
for (int i = 10; i > 1; i -= 2) {
assertTrue(r.contains(i));
}
r = range(10, 0, -2);
for (int i = 10; i > 0; i -= 2) {
assertTrue(r.contains(i));
}
r = range(10, -1, -2);
for (int i = 10; i >= 0; i -= 2) {
assertTrue(r.contains(i));
}
////
r = range(20, 50, 4);
for (int i = 20; i < 50; i += 4) {
assertTrue(r.contains(i));
}
}
@Test
public void testToArray_0args() {
Range r = range(-10, 10, 2);
Object[] arr = r.toArray();
assertEquals(10, arr.length);
for (int i = 0, j = -10; i < arr.length; ++i, j += 2) {
assertEquals(j, arr[i]);
}
r = range(10, -10, -2);
arr = r.toArray();
assertEquals(10, arr.length);
for (int i = 0, j = 10; i < arr.length; ++i, j -= 2) {
assertEquals(j, arr[i]);
}
}
@Test
public void testToArray_GenericType() {
Range r = range(37, 21, -1);
Integer[] input = new Integer[4];
Integer[] array = r.toArray(input);
assertFalse(input == array);
assertEquals(r.size(), array.length);
input = new Integer[50];
for (int i = 0; i < input.length; ++i) {
input[i] = Integer.valueOf(-1);
}
array = r.toArray(input);
assertTrue(array == input);
assertNull(input[r.size()]);
for (int i = r.size() + 1; i < input.length; ++i) {
assertEquals(Integer.valueOf(-1), input[i]);
}
}
public void testContainsAll() {
Range r = range(20, 3, -3);
Set<Integer> set = new HashSet<>();
set.add(20);
set.add(14);
set.add(11);
assertTrue(r.containsAll(set));
set.add(19);
assertFalse(r.containsAll(set));
}
@Test(expected = UnsupportedOperationException.class)
public void testAdd() {
range(10).add(1);
}
@Test(expected = UnsupportedOperationException.class)
public void testRemove() {
range(10).remove(1);
}
@Test(expected = UnsupportedOperationException.class)
public void testAddAll() {
range(2, 12).addAll(new HashSet<>());
}
@Test(expected = UnsupportedOperationException.class)
public void testRemoveAll() {
range(2, 12).removeAll(new HashSet<>());
}
@Test(expected = UnsupportedOperationException.class)
public void testRetainAll() {
range(2, 12).retainAll(new HashSet<>());
}
@Test(expected = UnsupportedOperationException.class)
public void testClear() {
range(5).clear();
}
}
As always, any critique is much appreciated.
Collection
, I would add a simpleasList
method that would iterate and create a List from the range (because the way it is now, it seems the only use of being a Collection is to pass it to the ArrayList constructor. It would also remove half the code - less chance of bug.) For example: github.com/smaspe/FunctionalIterables/blob/master/src/main/java/… \$\endgroup\$