在C ++中陷入Pythonish整数范围后,我重写了Java中的功能。在Python中,您可以说:
>>> range(38, 0, -3)
[38, 35, 32, 29, 26, 23, 20, 17, 14, 11, 8, 5, 2]
在Java中,您可以说:
for (int i : range(10)) {
...
}
,或者,如果需要的话:
List<Integer> list = new ArrayList<>(range(100, -100, -7));
实施
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();
}
}
和以往一样,任何批评都是非常感谢。
#1 楼
如果您以Java 8为目标,则应该改用IntStream
和PrimitiveIterator.OfInt
。您可以使用IntStream
进行更多有趣的操作,并且原始类型比盒装类型更有效。实际上,已经有了
IntStream.range(startInclusive, endExclusive)
函数。您可以简单地将.map()
转换为另一个IntStream
,而不是1。您具有三个
step
函数和三个对应的range(…)
构造函数。我建议您做出设计决定并坚持下去。在PEP 20(Python的禅宗)中:应该有一种-最好只有一种-显而易见的方法。
假设需要三个静态
Range(…)
函数,那么它们都应该调用一个range(…)
构造函数。Python的文档调用了三个参数
private Range(int start, int stop, int step)
,start
和stop
,您也应该这样做。或者,采用Java 8的step
,startInclusive
术语。我认为使用一个
endExclusive
类会更好。它的RangeIterator
方法可以根据hasNext()
是正还是负来决定要做什么。由于迭代器不是静态内部类,因此它可以访问外部的
step
,start
和stop
类而不是复制。如果扩展
step
而不是从头开始实现AbstractCollection
,则可以编写更少的代码。要实现不可修改的集合,程序员只需要扩展此类并为
Collection
和iterator
方法提供实现即可。 (由size
方法返回的迭代器必须实现iterator
和hasNext
。)为了效率,我还将覆盖
next
。使用一些数学运算而不是所有这些条件运算,可以更好地实现contains()
和size()
。有了以上建议的所有更改,代码可能会少得多:
public class Range extends AbstractCollection<Integer> implements Iterable<Integer> {
private final int start, stop, step;
public static Range range(int start, int stop, int step) {
return new Range(start, stop, step);
}
public static Range range(int start, int stop) {
return range(start, stop, 1);
}
public static Range range(int stop) {
return range(0, stop);
}
private Range(int start, int stop, int step) {
if (step == 0) {
throw new IllegalArgumentException("The step must not be zero.");
}
this.start = start;
this.stop = stop;
this.step = step;
}
@Override
public int size() {
return Math.max(0, step >= 0 ? (stop + step - 1 - start) / step
: (stop + step + 1 - start) / step);
}
@Override
public boolean contains(Object o) {
try {
int n = (int)o;
boolean inBounds = step >= 0 ? (start <= n) && (n < stop)
: (start >= n) && (n > stop);
return inBounds && (n - start) % step == 0;
} catch (ClassCastException notAnInt) {
return false;
}
}
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
private int value = start;
@Override
public boolean hasNext() {
return step >= 0 ? value < stop : value > stop;
}
@Override
public Integer next() {
if (!hasNext()) {
throw new NoSuchElementException("Iteration exceeded.");
}
try {
return value;
} finally {
value += step;
}
}
};
}
}
评论
\ $ \ begingroup \ $
但是ArrayList不接受AbstractCollection作为构造函数参数(在这种情况下,我怀疑这是实现Collection的主要用途)
\ $ \ endgroup \ $
– njzk2
2015年12月1日在22:05
\ $ \ begingroup \ $
@ njzk2是什么意思?为什么这么重要?
\ $ \ endgroup \ $
– 200_success
2015年12月1日23:07
\ $ \ begingroup \ $
范围调用看起来很奇怪Range.range(…)。在这种情况下,呼叫链会更好吗? Range.stop(不包含).start(包含).step(步骤)
\ $ \ endgroup \ $
–Eyal
2015年12月2日,0:08
\ $ \ begingroup \ $
@Eyal range(开始,停止,步进)是我们要在此处尝试模拟的Python模型。如果Range.range(...)太麻烦,则导入静态变量可能会有所帮助。
\ $ \ endgroup \ $
– 200_success
2015年12月2日,0:10
\ $ \ begingroup \ $
@ 200_success已确认。虽然有些事情在一种语言上效果很好,但在另一种语言上效果不佳。我们应该以捕获原始功能的目的为目标,并以java的方式实现它。
\ $ \ endgroup \ $
–Eyal
2015年12月2日,0:14
#2 楼
小点。步骤为零
是奇怪的错误消息。实际解释问题是否更好,即。
步骤不能为零
用户(希望)知道他们提供了哪些参数,您需要告诉他们的是参数无效。
正如@ 200_success在注释中指出的那样,Python遵循此规则并附带自己的错误消息:
Python 2
ValueError:range()步骤参数不能为零
Python 3
ValueError:range()arg 3不能为零
评论
\ $ \ begingroup \ $
我认为那一点很重要
\ $ \ endgroup \ $
–法老王
2015年12月2日,中午12:00
\ $ \ begingroup \ $
另外;请告诉它应该是什么。例如:步骤是0。它应该大于0。这似乎“太多了”,但是人们需要更多的脑力处理方法。
\ $ \ endgroup \ $
–RobAu
15年2月2日在14:40
#3 楼
在Python 3中,range
返回的序列正是您所创建的。但是,在Python 2中,range
会急切生成一个列表,该列表会将您的代码切成单个函数,并返回一个整数列表。 (我不知道Java不会显示示例代码。)我并不是说您应该做出任何不同,只是指出这一点。此外,我不确定不兼容的类型是否会导致返回false或返回例外。也许您可以为此行为提供一些优先权?
评论
\ $ \ begingroup \ $
尽管大多数流行的Python软件包都支持Python3。当然,有些人陷入了Python 2的世界,但是现在人们越来越多地鼓励人们移植/使用Python 3。
\ $ \ endgroup \ $
–Wayne Werner
2015年12月2日,14:09
评论
而不是实现Collection,我将添加一个简单的asList方法,该方法将迭代并从范围中创建一个List(因为现在是这样,似乎成为Collection的唯一用途是将其传递给ArrayList构造函数。它将还要删除一半的代码-减少错误的机会。)例如:github.com/smaspe/FunctionalIterables/blob/master/src/main/java/…后续问题
当然,如果可以选择Jython ...;)