如何在Java中表示一个区间?

69

假设一个整数应该在范围[0...2147483647]内。

我想检查一个整数变量是否在这个范围内。我知道可以通过简单的if-else语句来实现,但是否有更有效的方法来检查它是否在这个范围内?

我不想这样做:

if (foo >= 0 && foo <= 2147483647) 
{
    // do something
}

如果你在询问运行时效率(性能),那么恐怕你的 if 语句会获胜。不过,我同意这个观察结果不应该主导你的决策。对于99%的应用程序来说,维护效率才是更重要的。 - Ole V.V.
3
@OleV.V. 我甚至会简化为 if(foo >= 0) …,因为根本没有必要检查一个 int 是否 <=Integer.MAX_VALUE。但我怀疑提问者会不会阅读这些评论,因为缺席五年后回来的可能性很小。 - Holger
@OleV.V.,在调用此功能时需要注意频率和方式。条件语句会使流水线变为100%挤压状态。如果您可以通过其他“固态”数学技巧强制执行夹紧范围,那么效果会更好。 - alife
9个回答

69

Apache Commons Lang有一个用于处理任意范围的Range

Range<Integer> test = Range.between(1, 3);
System.out.println(test.contains(2));
System.out.println(test.contains(4));

Guava 的Range具有类似的 API。

如果您只想检查一个数字是否适合长整型或整型值,可以尝试使用 BigDecimal。它提供了longValueExactintValueExact方法,如果值超出这些精度范围会抛出异常。


2
是的,但你认为 .contains(...) 是如何实现的呢? ;) (肯定是用 if/else :)) - PhD
你知道它是否能够处理外部边缘吗?(例如int)像<=-5或>=12这样的东西? - granadaCoder
回答我自己的问题。 "是" 是答案。请参阅: Range.atMost 和 Range.atLeast。https://guava.dev/releases/snapshot/api/docs/com/google/common/collect/Range.html - granadaCoder
这个 Range 的问题在于它似乎缺少类似于 addWrap()subtractWrap() 的概念,当你需要将一个值向下减去到最小值以下时,以包装到最大值,或者类似地对一个值进行增加/添加,以包装到最大值时,这两个概念都非常有用。如果需要,甚至可以使用 % 并允许浮点数。这些很容易自己实现,但是我认为它们至少应该是 Math 方法,以利用可能存在的任何本地级别的原子操作。 - alife

33

你可以创建一个类来表示这个

public class Range
{
    private int low;
    private int high;

    public Range(int low, int high){
        this.low = low;
        this.high = high;
    }

    public boolean contains(int number){
        return (number >= low && number <= high);
    }
}

用例示例:

Range range = new Range(0, 2147483647);

if (range.contains(foo)) {
    //do something
}

我现在明白你试图避免使用if语句了。我原以为你只是不喜欢if语句中的比较语法...哦,好吧。 - ScArcher2
使用此实现,您无法表示空范围(假设 low <= high)。区间必须是半开放的 [low, high) 或者使用 extent 代替 high - Richard

20

我知道这是一个很老的问题,但是使用Java 8的Streams,您可以像这样获取一系列int

// gives an IntStream of integers from 0 through Integer.MAX_VALUE
IntStream.rangeClosed(0, Integer.MAX_VALUE); 

然后你可以这样做:

if (IntStream.rangeClosed(0, Integer.MAX_VALUE).matchAny(n -> n == A)) {
    // do something
} else {
    // do something else 
}

17
这个解决方案非常慢。你需要进行20亿次比较,而不是2次。 - Michael
1
@Michael 最多需要进行20亿次比较,但最好的情况下只有一个A。另一方面,你只需要进行一次实际比较,即A >= 0 - Holger

12

您可以使用 java.time.temporal.ValueRange,它接受 long 并且也可以与 int 一起使用:

int a = 2147;

//Use java 8 java.time.temporal.ValueRange. The range defined
//is inclusive of both min and max 
ValueRange range = ValueRange.of(0, 2147483647);

if(range.isValidValue(a)) {
    System.out.println("in range");
}else {
    System.out.println("not in range");
}

1
我喜欢这个,不幸的是它在Time包中,但在许多其他情况下也可以使用。 - Woodsman

8

如果你要检查很多区间,我建议使用区间树


3
有没有已知的Java库实现了区间树? - dokaspar

5

无论你如何努力优化这个不太密集的计算,你都会有一个if-check :) 你可以从数字中减去上限,如果结果为正数,那么你就知道超出了范围。你可以使用一些布尔位移逻辑来解决问题,甚至可以使用费马定理(开玩笑:)。但是,关键是“为什么”需要优化此比较?目的是什么?


2
我和 OP 一样,这种过程式检查总是感觉很尴尬。如果可以的话,我更喜欢声明性合同,即使前者最终会减少到后者。 - Michael Petrotta
我需要进行优化,因为当A是一个非常大的数字时,我发现它运行得非常慢。如果我选择在if...else语句中扩展并执行其他检查,那该怎么办?>_< - 황현정
3
你肯定在开玩笑吧,慢?是说你已经测过它的性能很差? - StaxMan
不好意思,我的意思是在那个if else语句中还有其他检查,不仅仅是那一个... >_< 所以我在想是否有其他方法。 - 황현정

1
对于一系列的 Comparable,我使用以下代码:

public class Range<T extends Comparable<T>> {

    /**
     * Include start, end in {@link Range}
     */
    public enum Inclusive {START,END,BOTH,NONE }

    /**
     * {@link Range} start and end values
     */
    private T start, end;
    private Inclusive inclusive;

    /**
     * Create a range with {@link Inclusive#START}
     * @param start
     *<br/> Not null safe
     * @param end
     *<br/> Not null safe
     */
    public Range(T start, T end) {  this(start, end, null); }

    /**
     * @param start
     *<br/> Not null safe
     * @param end
     *<br/> Not null safe
     *@param inclusive
     *<br/>If null {@link Inclusive#START} used
     */
    public Range(T start, T end, Inclusive inclusive) {

        if((start == null) || (end == null)) {
            throw new NullPointerException("Invalid null start / end value");
        }
        setInclusive(inclusive);

        if( isBigger(start, end) ) {
            this.start = end;   this.end   = start;
        }else {
            this.start = start;  this.end   = end;
        }
    }

    /**
     * Convenience method
     */
    public boolean isBigger(T t1, T t2) { return t1.compareTo(t2) > 0; }

    /**
     * Convenience method
     */
    public boolean isSmaller(T t1, T t2) { return t1.compareTo(t2) < 0; }

    /**
     * Check if this {@link Range} contains t
     *@param t
     *<br/>Not null safe
     *@return
     *false for any value of t, if this.start equals this.end
     */
    public boolean contains(T t) { return contains(t, inclusive); }

    /**
     * Check if this {@link Range} contains t
     *@param t
     *<br/>Not null safe
     *@param inclusive
     *<br/>If null {@link Range#inclusive} used
     *@return
     *false for any value of t, if this.start equals this.end
     */
    public boolean contains(T t, Inclusive inclusive) {

        if(t == null) {
            throw new NullPointerException("Invalid null value");
        }

        inclusive = (inclusive == null) ? this.inclusive : inclusive;

        switch (inclusive) {
            case NONE:
                return ( isBigger(t, start) && isSmaller(t, end) );
            case BOTH:
                return ( ! isBigger(start, t)  && ! isBigger(t, end) ) ;
            case START: default:
                return ( ! isBigger(start, t)  &&  isBigger(end, t) ) ;
            case END:
                return ( isBigger(t, start)  &&  ! isBigger(t, end) ) ;
        }
    }

    /**
     * Check if this {@link Range} contains other range
     * @return
     * false for any value of range, if this.start equals this.end
     */
    public boolean contains(Range<T> range) {
        return contains(range.start) && contains(range.end);
    }

    /**
     * Check if this {@link Range} intersects with other range
     * @return
     * false for any value of range, if this.start equals this.end
     */
    public boolean intersects(Range<T> range) {
        return contains(range.start) || contains(range.end);
    }

    /**
    * Get {@link #start}
    */
    public T getStart() { return start; }

    /**
    * Set {@link #start}
    * <br/>Not null safe
    * <br/>If start > end they are switched
    */
    public Range<T> setStart(T start) {

        if(start.compareTo(end)>0) {
            this.start = end;
            this.end  = start;
        }else {
            this.start = start;
        }
        return this;
    }

    /**
    * Get {@link #end}
    */
    public T getEnd() {  return end;  }

    /**
    * Set {@link #end}
    * <br/>Not null safe
    *  <br/>If start > end they are switched
    */
    public  Range<T> setEnd(T end) {

        if(start.compareTo(end)>0) {
            this.end  = start;
            this.start = end;
        }else {
            this.end = end;
        }
        return this;
    }

    /**
    * Get {@link #inclusive}
    */
    public Inclusive getInclusive() { return inclusive; }

    /**
    * Set {@link #inclusive}
    * @param inclusive
    *<br/>If null {@link Inclusive#START} used
    */
    public  Range<T> setInclusive(Inclusive inclusive) {

        this.inclusive = (inclusive == null) ? Inclusive.START : inclusive;
        return this;
    }
}

(这是一个较为简短的版本。完整代码可在此处找到。)


0
如果您使用Spring,那么您可以依赖于org.springframework.data.domain,它非常完整,包括有界和无界范围。

0
import java.util.Arrays;

class Soft{
    public static void main(String[] args){
        int[] nums=range(9, 12);
        System.out.println(Arrays.toString(nums));
    }
    static int[] range(int low, int high){
        int[] a=new int[high-low];
        for(int i=0,j=low;i<high-low;i++,j++){
            a[i]=j;
        }
        return a;

    }
}

我的代码类似于 Python 的 range :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接