简介

在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();
    }
}


和以往一样,任何批评都是非常感谢。

评论

而不是实现Collection,我将添加一个简单的asList方法,该方法将迭代并从范围中创建一个List(因为现在是这样,似乎成为Collection的唯一用途是将其传递给ArrayList构造函数。它将还要删除一半的代码-减少错误的机会。)例如:github.com/smaspe/FunctionalIterables/blob/master/src/main/java/…

后续问题

当然,如果可以选择Jython ...;)

#1 楼

如果您以Java 8为目标,则应该改用IntStreamPrimitiveIterator.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)startstop,您也应该这样做。或者,采用Java 8的stepstartInclusive术语。


我认为使用一个endExclusive类会更好。它的RangeIterator方法可以根据hasNext()是正还是负来决定要做什么。
由于迭代器不是静态内部类,因此它可以访问外部的stepstartstop类而不是复制。


如果扩展step而不是从头开始实现AbstractCollection,则可以编写更少的代码。


要实现不可修改的集合,程序员只需要扩展此类并为Collectioniterator方法提供实现即可。 (由size方法返回的迭代器必须实现iteratorhasNext。)


为了效率,我还将覆盖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