!832 【6.x】添加支持处理区间的工具类

Merge pull request !832 from Createsequence/v6-range
This commit is contained in:
Looly 2022-10-08 09:19:57 +00:00 committed by Gitee
commit 910caa2f06
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
6 changed files with 1876 additions and 0 deletions

View File

@ -0,0 +1,587 @@
package cn.hutool.core.lang.range;
import cn.hutool.core.text.CharSequenceUtil;
import java.util.Objects;
import java.util.function.Predicate;
/**
* <p>边界对象描述具有特定上界或下界的单侧无界的区间
*
* <h3>边界的类型</h3>
* <p>边界根据其{@link #getType()}所获得的类型可用于描述基于边界值<em>t</em>的不等式:
* <ul>
* <li>{@link #noneLowerBound()}{@code {x | x > -}}</li>
* <li>{@link #noneUpperBound()}{@code {x | x < +}}</li>
* <li>{@link #greaterThan}{@code {x | x > t}}</li>
* <li>{@link #atLeast}{@code {x | x >= t}}</li>
* <li>{@link #lessThan}{@code {x | x < t}}</li>
* <li>{@link #atMost}{@code {x | x <= t}}</li>
* </ul>
* 当作为{@link Predicate}使用时可用于判断入参对象是否能满足当前实例所对应的不等式
*
* <h3>边界的比较</h3>
* <p>边界对象本身实现了{@link Comparable}接口
* 当使用{@link Comparable#compareTo}比较两个边界对象时
* 返回的比较值表示两个边界对象对应的点在实数轴上从左到右的先后顺序<br>
* 比如
* 若令当前边界点为<em>t1</em>另一边界点为<em>t2</em>则有
* <ul>
* <li>-1<em>t1</em><em>t2</em>的左侧</li>
* <li>0<em>t1</em><em>t2</em>所表示的点彼此重合</li>
* <li>-1<em>t1</em><em>t2</em>的右侧</li>
* </ul>
*
* @param <T> 边界值类型
* @author huangchengxing
* @see BoundType
* @see BoundedRange
* @since 6.0.0
*/
public interface Bound<T extends Comparable<? super T>> extends Predicate<T>, Comparable<Bound<T>> {
/**
* 无穷小
*/
String INFINITE_MIN = "-\u221e";
/**
* 无穷大
*/
String INFINITE_MAX = "+\u221e";
/**
* 无限小的左边界
*/
@SuppressWarnings("rawtypes")
NoneLowerBound NONE_LOWER_BOUND = new NoneLowerBound();
/**
* 无限大的右边界
*/
@SuppressWarnings("rawtypes")
NoneUpperBound NONE_UPPER_BOUND = new NoneUpperBound();
/**
* {@code {x | x > -}}
*
* @param <T> 边界值类型
* @return 区间
*/
@SuppressWarnings("unchecked")
static <T extends Comparable<? super T>> Bound<T> noneLowerBound() {
return NONE_LOWER_BOUND;
}
/**
* {@code {x | x < +}}
*
* @param <T> 边界值类型
* @return 区间
*/
@SuppressWarnings("unchecked")
static <T extends Comparable<? super T>> Bound<T> noneUpperBound() {
return NONE_UPPER_BOUND;
}
/**
* {@code {x | x > min}}
*
* @param min 最小值
* @param <T> 边界值类型
* @return 区间
*/
static <T extends Comparable<? super T>> Bound<T> greaterThan(final T min) {
return new FiniteBound<>(Objects.requireNonNull(min), BoundType.OPEN_LOWER_BOUND);
}
/**
* {@code {x | x >= min}}
*
* @param min 最小值
* @param <T> 边界值类型
* @return 区间
*/
static <T extends Comparable<? super T>> Bound<T> atLeast(final T min) {
return new FiniteBound<>(Objects.requireNonNull(min), BoundType.CLOSE_LOWER_BOUND);
}
/**
* {@code {x | x < max}}
*
* @param max 最大值
* @param <T> 边界值类型
* @return 区间
*/
static <T extends Comparable<? super T>> Bound<T> lessThan(final T max) {
return new FiniteBound<>(Objects.requireNonNull(max), BoundType.OPEN_UPPER_BOUND);
}
/**
* {@code {x | x <= max}}
*
* @param max 最大值
* @param <T> 边界值类型
* @return 区间
*/
static <T extends Comparable<? super T>> Bound<T> atMost(final T max) {
return new FiniteBound<>(Objects.requireNonNull(max), BoundType.CLOSE_UPPER_BOUND);
}
/**
* 获取边界值
*
* @return 边界值
*/
T getValue();
/**
* 获取边界类型
*
* @return 边界类型
*/
BoundType getType();
/**
* 检验指定值是否在当前边界表示的范围内
*
* @param t 要检验的值不允许为{@code null}
* @return 是否
*/
@Override
boolean test(T t);
/**
* <p>比较另一边界与当前边界在坐标轴上位置的先后顺序<br>
* 若令当前边界为<em>t1</em>另一边界为<em>t2</em>则有
* <ul>
* <li>-1<em>t1</em><em>t2</em>的左侧</li>
* <li>0<em>t1</em><em>t2</em>的重合</li>
* <li>-1<em>t1</em><em>t2</em>的右侧</li>
* </ul>
*
* @param bound 边界
* @return 位置
*/
@Override
int compareTo(final Bound<T> bound);
/**
* 获取{@code "[value"}{@code "(value"}格式的字符串
*
* @return 字符串
*/
String descBound();
/**
* 对当前边界取反
*
* @return 取反后的边界
*/
@Override
Bound<T> negate();
/**
* 将当前实例转为一个区间
*
* @return 区间
*/
BoundedRange<T> toRange();
/**
* 获得当前实例对应的{@code {x| x >= xxx}}格式的不等式字符串
*
* @return 字符串
*/
@Override
String toString();
/**
* 由一个有限值构成的边界
*
* @param <T> 边界值类型
*/
class FiniteBound<T extends Comparable<? super T>> implements Bound<T> {
/**
* 边界值
*/
private final T value;
/**
* 边界类型
*/
private final BoundType type;
/**
* 构造
*
* @param value 边界值
* @param type 边界类型
*/
FiniteBound(final T value, final BoundType type) {
this.value = value;
this.type = type;
}
/**
* 获取边界值
*
* @return 边界值
*/
@Override
public T getValue() {
return value;
}
/**
* 获取边界类型
*
* @return 边界类型
*/
@Override
public BoundType getType() {
return type;
}
/**
* 检验指定值是否在当前边界表示的范围内
*
* @param t 要检验的值不允许为{@code null}
* @return 是否
*/
@Override
public boolean test(final T t) {
final BoundType bt = this.getType();
final int compareValue = getValue().compareTo(t);
// 与边界值相等
if (compareValue == 0) {
return bt.isClose();
}
// 小于或大于边界值
return compareValue > 0 ? bt.isUpperBound() : bt.isLowerBound();
}
/**
* <p>比较另一边界与当前边界在坐标轴上位置的先后顺序<br>
* 若令当前边界为<em>t1</em>另一边界为<em>t2</em>则有
* <ul>
* <li>-1<em>t1</em><em>t2</em>的左侧</li>
* <li>0<em>t1</em><em>t2</em>的重合</li>
* <li>-1<em>t1</em><em>t2</em>的右侧</li>
* </ul>
*
* @param bound 边界
* @return 位置
*/
@Override
public int compareTo(final Bound<T> bound) {
// 另一边界为无限小的左边界则当前边界必然靠后
if (bound instanceof Bound.NoneLowerBound) {
return 1;
}
// 另一边界为无限大的右边界则当前边界必然靠前
if (bound instanceof Bound.NoneUpperBound) {
return -1;
}
// 两值不相等直接比较边界值
if (!Objects.equals(getValue(), bound.getValue())) {
return getValue().compareTo(bound.getValue());
}
// 两边界值相等
return compareIfSameBoundValue(bound);
}
/**
* 当两个边界的值不相等时判断它们在坐标轴上位置的先后顺序
*/
private int compareIfSameBoundValue(final Bound<T> bound) {
final BoundType bt1 = this.getType();
final BoundType bt2 = bound.getType();
// 两边界类型相同说明连边界重合
if (bt1 == bt2) {
return 0;
}
// 一为左边界一为右边界则左边界恒在右边界后
if (bt1.isDislocated(bt2)) {
return bt1.isLowerBound() ? 1 : -1;
}
// 都为左边界则封闭边界在前若都为右边界则封闭边界在后
return bt1.isLowerBound() ?
Integer.compare(bt1.getCode(), bt2.getCode()) : Integer.compare(bt1.getCode(), bt2.getCode());
}
/**
* 获取{@code "[value"}{@code "(value"}格式的字符串
*
* @return 字符串
*/
@Override
public String descBound() {
final BoundType bt = getType();
return bt.isLowerBound() ? bt.getSymbol() + getValue() : getValue() + bt.getSymbol();
}
/**
* 对当前边界取反
*
* @return 取反后的边界
*/
@Override
public Bound<T> negate() {
return new FiniteBound<>(value, getType().negate());
}
/**
* 将当前实例转为一个区间
*
* @return 区间
*/
@Override
public BoundedRange<T> toRange() {
return getType().isLowerBound() ?
new BoundedRange<>(this, Bound.noneUpperBound()) : new BoundedRange<>(Bound.noneLowerBound(), this);
}
/**
* 两实例是否相等
*
* @param o 另一实例
* @return 是否
*/
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final FiniteBound<?> that = (FiniteBound<?>)o;
return value.equals(that.value) && type == that.type;
}
/**
* 获取哈希值
*
* @return 哈希值
*/
@Override
public int hashCode() {
return Objects.hash(value, type);
}
/**
* 获得当前实例对应的{@code {x| x >= xxx}}格式的不等式字符串
*
* @return 字符串
*/
@Override
public String toString() {
return CharSequenceUtil.format(
"{x | x {} {}}", type.getOperator(), value
);
}
}
/**
* 无限小的左边界
*
* @param <T> 边界值类型
*/
class NoneLowerBound<T extends Comparable<? super T>> implements Bound<T> {
private NoneLowerBound() {
}
/**
* 获取边界值
*
* @return 边界值
*/
@Override
public T getValue() {
return null;
}
/**
* 获取边界类型
*
* @return 边界类型
*/
@Override
public BoundType getType() {
return BoundType.OPEN_LOWER_BOUND;
}
/**
* 检验指定值是否在当前边界表示的范围内
*
* @param t 要检验的值不允许为{@code null}
* @return 是否
*/
@Override
public boolean test(final T t) {
return true;
}
/**
* <p>比较另一边界与当前边界在坐标轴上位置的先后顺序<br>
* 若令当前边界为<em>t1</em>另一边界为<em>t2</em>则有
* <ul>
* <li>-1<em>t1</em><em>t2</em>的左侧</li>
* <li>0<em>t1</em><em>t2</em>的重合</li>
* <li>-1<em>t1</em><em>t2</em>的右侧</li>
* </ul>
*
* @param bound 边界
* @return 位置
*/
@Override
public int compareTo(final Bound<T> bound) {
return bound instanceof Bound.NoneLowerBound ? 0 : -1;
}
/**
* 获取{@code "[value"}{@code "(value"}格式的字符串
*
* @return 字符串
*/
@Override
public String descBound() {
return getType().getSymbol() + INFINITE_MIN;
}
/**
* 对当前边界取反
*
* @return 取反后的边界
*/
@Override
public Bound<T> negate() {
return this;
}
/**
* 将当前实例转为一个区间
*
* @return 区间
*/
@Override
public BoundedRange<T> toRange() {
return BoundedRange.all();
}
/**
* 获得当前实例对应的{@code { x | x >= xxx}}格式的不等式字符串
*
* @return 字符串
*/
@Override
public String toString() {
return "{x | x > -\u221e}";
}
}
/**
* 无限大的右边界
*
* @param <T> 边界值类型
*/
class NoneUpperBound<T extends Comparable<? super T>> implements Bound<T> {
private NoneUpperBound() {
}
/**
* 获取边界值
*
* @return 边界值
*/
@Override
public T getValue() {
return null;
}
/**
* 获取边界类型
*
* @return 边界类型
*/
@Override
public BoundType getType() {
return BoundType.OPEN_UPPER_BOUND;
}
/**
* 检验指定值是否在当前边界表示的范围内
*
* @param t 要检验的值不允许为{@code null}
* @return 是否
*/
@Override
public boolean test(final T t) {
return true;
}
/**
* <p>比较另一边界与当前边界在坐标轴上位置的先后顺序<br>
* 若令当前边界为<em>t1</em>另一边界为<em>t2</em>则有
* <ul>
* <li>-1<em>t1</em><em>t2</em>的左侧</li>
* <li>0<em>t1</em><em>t2</em>的重合</li>
* <li>-1<em>t1</em><em>t2</em>的右侧</li>
* </ul>
*
* @param bound 边界
* @return 位置
*/
@Override
public int compareTo(final Bound<T> bound) {
return bound instanceof Bound.NoneUpperBound ? 0 : 1;
}
/**
* 获取{@code "[value"}{@code "(value"}格式的字符串
*
* @return 字符串
*/
@Override
public String descBound() {
return INFINITE_MAX + getType().getSymbol();
}
/**
* 获得当前实例对应的{@code { x | x >= xxx}}格式的不等式字符串
*
* @return 字符串
*/
@Override
public String toString() {
return "{x | x < +\u221e}";
}
/**
* 对当前边界取反
*
* @return 取反后的边界
*/
@Override
public Bound<T> negate() {
return this;
}
/**
* 将当前实例转为一个区间
*
* @return 区间
*/
@Override
public BoundedRange<T> toRange() {
return BoundedRange.all();
}
}
}

View File

@ -0,0 +1,140 @@
package cn.hutool.core.lang.range;
/**
* 边界类型
*
* @author huangchengxing
* @since 6.0.0
*/
public enum BoundType {
/**
* 表示一个左闭区间等同于{@code {x | x >= a}}
*/
CLOSE_LOWER_BOUND("[", ">=", -2),
/**
* 表示一个左开区间等同于{@code {x | x > a}}
*/
OPEN_LOWER_BOUND("(", ">", -1),
/**
* 表示一个右开区间等同于{@code {x | x < a}}
*/
OPEN_UPPER_BOUND(")", "<", 1),
/**
* 表示一个右闭区间等同于{@code {x | x <= a}}
*/
CLOSE_UPPER_BOUND("]", "<=", 2);
/**
* 符号
*/
private final String symbol;
/**
* 运算符
*/
private final String operator;
/**
* 是否为开区间
*/
private final int code;
/**
* 构造
*/
BoundType(final String symbol, final String operator, final int code) {
this.symbol = symbol;
this.operator = operator;
this.code = code;
}
/**
* 获取符号
*
* @return 符号
*/
public String getSymbol() {
return symbol;
}
/**
* 获取code
*
* @return code
*/
public int getCode() {
return code;
}
/**
* 获取运算符
*
* @return 运算符
*/
public String getOperator() {
return operator;
}
/**
* 该边界类型是否与当前边界错位即一个的左边界一个是右边界
*
* @param boundType 另一边界类型
* @return 是否
*/
public boolean isDislocated(BoundType boundType) {
return code * boundType.code < 0;
}
/**
* 是下界
*
* @return 是否
*/
public boolean isLowerBound() {
return code < 0;
}
/**
* 是上界
*
* @return 是否
*/
public boolean isUpperBound() {
return code > 0;
}
/**
* 是闭区间
*
* @return 是否
*/
public boolean isClose() {
return (code & 1) == 0;
}
/**
* 是开区间
*
* @return 是否
*/
public boolean isOpen() {
return (code & 1) == 1;
}
/**
* 对边界类型取反
*
* @return 取反后的边界类型
*/
public BoundType negate() {
if (isLowerBound()) {
return isOpen() ? CLOSE_UPPER_BOUND : OPEN_UPPER_BOUND;
}
return isOpen() ? CLOSE_LOWER_BOUND : OPEN_LOWER_BOUND;
}
}

View File

@ -0,0 +1,572 @@
package cn.hutool.core.lang.range;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Opt;
import java.util.Objects;
import java.util.function.Predicate;
/**
* <p>参考<em>Guava</em><em>Range</em>实现用于描述作为上下界的两个{@link Bound}实例围成的一段区间<br>
* 作为{@link Predicate}使用时可检验指定值是否在区间中即指定值是否同时满足上下界的{@link Bound#test}方法
*
* <h3>区间的类型</h3>
* <p>支持通过工厂方法创建下述几种类型的区间
* <tr><th>区间 <th>数学定义 <th>工厂方法
* <tr><td>{@code (a, b)} <td>{@code {x | a < x < b}} <td>{@link #open}
* <tr><td>{@code [a, b]} <td>{@code {x | a <= x <= b}}<td>{@link #close}
* <tr><td>{@code (a, b]} <td>{@code {x | a < x <= b}} <td>{@link #openClose}
* <tr><td>{@code [a, b)} <td>{@code {x | a <= x < b}} <td>{@link #closeOpen}
* <tr><td>{@code (a, +)} <td>{@code {x | x > a}} <td>{@link #greaterThan}
* <tr><td>{@code [a, +)} <td>{@code {x | x >= a}} <td>{@link #atLeast}
* <tr><td>{@code (-, b)} <td>{@code {x | x < b}} <td>{@link #lessThan}
* <tr><td>{@code (-, b]} <td>{@code {x | x <= b}} <td>{@link #atMost}
* <tr><td>{@code (-, +)}<td>{@code {x}} <td>{@link #all}
* </table>
*
* <h3>空区间</h3>
* <p>根据数学定义当区间中无任何实数时认为该区间代表的集合为空集
* 用户可通过{@link #isEmpty}确认当前实例是否为空区间<br>
* 若实例上界<em>a</em>下界为<em>b</em>则当实例满足下述任意条件时认为其为一个空区间
* <ul>
* <li>{@code a > b}</li>
* <li>{@code [a, b)}{@code a == b}</li>
* <li>{@code (a, b)}{@code a == b}</li>
* <li>{@code (a, b]}{@code a == b}</li>
* </ul>
* 当通过工厂方法创建区间时若区间为空则会抛出{@link IllegalArgumentException},
* 但是通过交并操作仍有可能创建出满足上述描述的空区间
* 此时若空区间参与操作可能得到意外的结果
* 因此对通过非工厂方法得到的区间在操作前有必要通过{@link #isEmpty}进行检验
*
* @param <T> 边界值类型
* @author huangchengxing
* @see Bound
* @since 6.0.0
*/
public class BoundedRange<T extends Comparable<? super T>> implements Predicate<T> {
/**
* 双向无界的区间
*/
@SuppressWarnings("rawtypes")
private static final BoundedRange ALL = new BoundedRange(Bound.noneLowerBound(), Bound.noneUpperBound());
/**
* 下界
*/
private final Bound<T> lowerBound;
/**
* 上界
*/
private final Bound<T> upperBound;
/**
* 构建一个上下界皆无限大的区间{@code {x | - < x < +}}
*
* @return 区间
*/
@SuppressWarnings("unchecked")
static <T extends Comparable<? super T>> BoundedRange<T> all() {
return ALL;
}
/**
* 构建一个闭区间{@code {x | lowerBound <= x <= upperBound}}
*
* @param lowerBound 下界不能为空
* @param upperBound 上界不能为空
* @param <T> 边界值类型
* @return 区间
* @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出
* @throws NullPointerException 上界或下界为{@code null}时抛出
*/
static <T extends Comparable<? super T>> BoundedRange<T> close(final T lowerBound, final T upperBound) {
Objects.requireNonNull(lowerBound);
Objects.requireNonNull(upperBound);
return checkEmpty(
new BoundedRange<>(
Bound.atLeast(lowerBound), Bound.atMost(upperBound)
)
);
}
/**
* 构建一个开区间{@code {x | lowerBound < x < upperBound}}
*
* @param lowerBound 下界不能为空
* @param upperBound 上界不能为空
* @param <T> 边界值类型
* @return 区间
* @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出
* @throws NullPointerException 上界或下界为{@code null}时抛出
*/
static <T extends Comparable<? super T>> BoundedRange<T> open(final T lowerBound, final T upperBound) {
Objects.requireNonNull(lowerBound);
Objects.requireNonNull(upperBound);
return checkEmpty(
new BoundedRange<>(Bound.greaterThan(lowerBound), Bound.lessThan(upperBound))
);
}
/**
* 构建一个左闭右开区间{@code {x | lowerBound <= x < upperBound}}
*
* @param lowerBound 下界不能为空
* @param upperBound 上界不能为空
* @param <T> 边界值类型
* @return 区间
* @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出
* @throws NullPointerException 上界或下界为{@code null}时抛出
*/
static <T extends Comparable<? super T>> BoundedRange<T> closeOpen(final T lowerBound, final T upperBound) {
Objects.requireNonNull(lowerBound);
Objects.requireNonNull(upperBound);
return checkEmpty(
new BoundedRange<>(
Bound.atLeast(lowerBound),
Bound.lessThan(upperBound)
)
);
}
/**
* 构建一个左闭右开区间{@code {x | lowerBound < x <= upperBound}}
*
* @param lowerBound 下界不能为空
* @param upperBound 上界不能为空
* @param <T> 边界值类型
* @return 区间
* @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出
* @throws NullPointerException 上界或下界为{@code null}时抛出
*/
static <T extends Comparable<? super T>> BoundedRange<T> openClose(final T lowerBound, final T upperBound) {
Objects.requireNonNull(lowerBound);
Objects.requireNonNull(upperBound);
return checkEmpty(
new BoundedRange<>(
Bound.greaterThan(lowerBound),
Bound.atMost(upperBound)
)
);
}
/**
* {@code {x | lowerBound < x < +}}
*
* @param lowerBound 下界不能为空
* @param <T> 边界值类型
* @return 区间
* @throws NullPointerException 下界为{@code null}时抛出
* @see Bound#toRange()
*/
static <T extends Comparable<? super T>> BoundedRange<T> greaterThan(final T lowerBound) {
return Bound.greaterThan(lowerBound).toRange();
}
/**
* {@code {x | lowerBound < x < +}}
*
* @param lowerBound 下界不能为空
* @param <T> 边界值类型
* @return 区间
* @throws NullPointerException 下界为{@code null}时抛出
* @see Bound#toRange()
*/
static <T extends Comparable<? super T>> BoundedRange<T> atLeast(final T lowerBound) {
return Bound.atLeast(lowerBound).toRange();
}
/**
* {@code {x | - < x < upperBound}}
*
* @param upperBound 上界不能为空
* @param <T> 边界值类型
* @return 区间
* @throws NullPointerException 上界为{@code null}时抛出
* @see Bound#toRange()
*/
static <T extends Comparable<? super T>> BoundedRange<T> lessThan(final T upperBound) {
return Bound.lessThan(upperBound).toRange();
}
/**
* {@code {x | - < x <= max}}
*
* @param upperBound 上界不能为空
* @param <T> 边界值类型
* @return 区间
* @throws NullPointerException 上界为{@code null}时抛出
* @see Bound#toRange()
*/
static <T extends Comparable<? super T>> BoundedRange<T> atMost(final T upperBound) {
return Bound.atMost(upperBound).toRange();
}
/**
* 构造
*
* @param lowerBound 下界
* @param upperBound 上界
*/
BoundedRange(final Bound<T> lowerBound, final Bound<T> upperBound) {
this.lowerBound = lowerBound;
this.upperBound = upperBound;
}
// endregion
// region ========== 通用方法 ==========
/**
* 获取下界
*
* @return 下界
*/
public Bound<T> getLowerBound() {
return lowerBound;
}
/**
* 获取下界值
*
* @return 下界值
*/
public T getLowerBoundValue() {
return getLowerBound().getValue();
}
/**
* 是否有下界
*
* @return 是否
*/
public boolean hasLowerBound() {
return Objects.nonNull(getLowerBound().getValue());
}
/**
* 获取上界
*
* @return 上界
*/
public Bound<T> getUpperBound() {
return upperBound;
}
/**
* 获取上界值
*
* @return 上界值
*/
public T getUpperBoundValue() {
return getUpperBound().getValue();
}
/**
* 是否有上界
*
* @return 是否
*/
public boolean hasUpperBound() {
return Objects.nonNull(getUpperBound().getValue());
}
/**
* <p>当前区间是否为空 <br>
* 当由下界<em>left</em>与上界<em>right</em>构成的区间
* 符合下述任意条件时认为当前区间为空
* <ul>
* <li>对任何区间{@code left > right}</li>
* <li>对半开半闭区间{@code [left, right)}{@code left == right}</li>
* <li>对开区间{@code (left, right)}{@code left == right}</li>
* <li>对半开半闭区间{@code (left, right]}{@code left == right}</li>
* </ul>
*
* @return 是否
*/
public boolean isEmpty() {
Bound<T> low = getLowerBound();
Bound<T> up = getUpperBound();
if (low instanceof Bound.NoneLowerBound || up instanceof Bound.NoneUpperBound) {
return false;
}
int compareValue = low.getValue().compareTo(up.getValue());
if (compareValue < 0) {
return false;
}
// 上界小于下界时为空
return compareValue > 0
// 上下界的边界值相等且不为退化区间是为空
|| !(low.getType().isClose() && up.getType().isClose());
}
/**
* 输出当前区间的字符串格式为{@code "[a, b]"}
*
* @return 字符串
*/
@Override
public String toString() {
return getLowerBound().descBound() + ", " + getUpperBound().descBound();
}
/**
* 比较两个实例是否相等
*
* @param o 另一实例
* @return 是否
*/
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final BoundedRange<?> that = (BoundedRange<?>)o;
return lowerBound.equals(that.lowerBound) && upperBound.equals(that.upperBound);
}
/**
* 获取实例哈希值
*
* @return 哈希值
*/
@Override
public int hashCode() {
return Objects.hash(lowerBound, upperBound);
}
// endregion
// region ========== 判断交并集 ==========
/**
* {@code other}是否是当前区间的子集
*
* @param other 另一区间
* @return 是否
*/
public boolean isSuperset(final BoundedRange<T> other) {
return getLowerBound().compareTo(other.getLowerBound()) <= 0
&& getUpperBound().compareTo(other.getUpperBound()) >= 0;
}
/**
* {@code other}是否是当前区间的子集
*
* @param other 另一区间
* @return 是否
*/
public boolean isProperSuperset(final BoundedRange<T> other) {
return getLowerBound().compareTo(other.getLowerBound()) < 0
&& getUpperBound().compareTo(other.getUpperBound()) > 0;
}
/**
* 当前区间是否是{@code other}的子集
*
* @param other 另一区间
* @return 是否
*/
public boolean isSubset(final BoundedRange<T> other) {
return getLowerBound().compareTo(other.getLowerBound()) >= 0
&& getUpperBound().compareTo(other.getUpperBound()) <= 0;
}
/**
* 当前区间是否是{@code other}的真子集
*
* @param other 另一区间
* @return 是否
*/
public boolean isProperSubset(final BoundedRange<T> other) {
return getLowerBound().compareTo(other.getLowerBound()) > 0
&& getUpperBound().compareTo(other.getUpperBound()) < 0;
}
/**
* {@code other}是否与当前区间不相交
*
* @param other 另一区间
* @return 是否
*/
public boolean isDisjoint(final BoundedRange<T> other) {
return getLowerBound().compareTo(other.getUpperBound()) > 0
|| getUpperBound().compareTo(other.getLowerBound()) < 0;
}
/**
* {@code other}是否与当前区间相交
*
* @param other 另一区间
* @return 是否
*/
public boolean isIntersected(final BoundedRange<T> other) {
return !isDisjoint(other);
}
/**
* {@code other}与当前区间是否相等
*
* @param other 另一区间
* @return 是否
*/
public boolean isEquals(final BoundedRange<T> other) {
Objects.requireNonNull(other);
return other.getLowerBound().compareTo(getLowerBound()) == 0
&& other.getUpperBound().compareTo(getUpperBound()) == 0;
}
/**
* 指定值是否在当前区间内
*
* @param value 要检测的值
* @return 是否
*/
@Override
public boolean test(final T value) {
return getLowerBound()
.and(getUpperBound())
.test(value);
}
// endregion
// region ========== 交并集操作 ==========
/**
* {@code other}与当前区间相交则将其与当前区间合并
*
* @param other 另一区间
* @return 合并后的新区间若两区间不相交则返回当前集合
*/
public BoundedRange<T> unionIfIntersected(final BoundedRange<T> other) {
Objects.requireNonNull(other);
if (isDisjoint(other)) {
return this;
}
return new BoundedRange<>(
getMin(getLowerBound(), other.getLowerBound()),
getMax(getUpperBound(), other.getUpperBound())
);
}
/**
* 获得包含当前区间与指定区间的最小的区间
*
* @param other 另一区间
* @return 包含当前区间与指定区间的最小的区间
*/
public BoundedRange<T> span(final BoundedRange<T> other) {
Objects.requireNonNull(other);
return new BoundedRange<>(
getMin(getLowerBound(), other.getLowerBound()),
getMax(getUpperBound(), other.getUpperBound())
);
}
/**
* {@code other}与当前区间不相连则获得两区间中间的间隔部分
*
* @param other 另一区间
* @return 代表间隔部分的区间若两区间相交则返回{@code null}
*/
public BoundedRange<T> gap(final BoundedRange<T> other) {
Objects.requireNonNull(other);
if (isIntersected(other)) {
return null;
}
return new BoundedRange<>(
getMin(getUpperBound(), other.getUpperBound()).negate(),
getMax(getLowerBound(), other.getLowerBound()).negate()
);
}
/**
* {@code other}与当前区间相交则获得该区间与当前区间的交集
*
* @param other 另一区间
* @return 代表交集的区间若无交集则返回{@code null}
*/
public BoundedRange<T> intersection(final BoundedRange<T> other) {
Objects.requireNonNull(other);
if (isDisjoint(other)) {
return null;
}
return new BoundedRange<>(
getMax(getLowerBound(), other.getLowerBound()),
getMin(getUpperBound(), other.getUpperBound())
);
}
/**
* 截取当前区间中大于{@code min}的部分{@code min}不在该区间中则返回当前区间本身
*
* @param min 最大的左值
* @return 区间
*/
public BoundedRange<T> subGreatThan(final T min) {
return Opt.ofNullable(min)
.filter(this)
.map(t -> new BoundedRange<>(Bound.greaterThan(t), getUpperBound()))
.orElse(this);
}
/**
* 截取当前区间中大于等于{@code min}的部分{@code min}不在该区间中则返回当前区间本身
*
* @param min 最大的左值
* @return 区间
*/
public BoundedRange<T> subAtLeast(final T min) {
return Opt.ofNullable(min)
.filter(this)
.map(t -> new BoundedRange<>(Bound.atLeast(t), getUpperBound()))
.orElse(this);
}
/**
* 截取当前区间中小于{@code max}的部分{@code max}不在该区间中则返回当前区间本身
*
* @param max 最大的左值
* @return 区间
*/
public BoundedRange<T> subLessThan(final T max) {
return Opt.ofNullable(max)
.filter(this)
.map(t -> new BoundedRange<>(getLowerBound(), Bound.lessThan(max)))
.orElse(this);
}
/**
* 截取当前区间中小于等于{@code max}的部分{@code max}不在该区间中则返回当前区间本身
*
* @param max 最大的左值
* @return 区间
*/
public BoundedRange<T> subAtMost(final T max) {
return Opt.ofNullable(max)
.filter(this)
.map(t -> new BoundedRange<>(getLowerBound(), Bound.atMost(max)))
.orElse(this);
}
// endregion
private static <T extends Comparable<? super T>> Bound<T> getMin(Bound<T> b1, Bound<T> b2) {
return b1.compareTo(b2) <= 0 ? b1 : b2;
}
private static <T extends Comparable<? super T>> Bound<T> getMax(Bound<T> b1, Bound<T> b2) {
return b1.compareTo(b2) >= 0 ? b1 : b2;
}
private static <T extends Comparable<? super T>> BoundedRange<T> checkEmpty(BoundedRange<T> range) {
Assert.isFalse(range.isEmpty(), "{} is a empty range", range);
return range;
}
}

View File

@ -0,0 +1,211 @@
package cn.hutool.core.lang.range;
import org.junit.Assert;
import org.junit.Test;
/**
* test for {@link Bound}
*/
public class BoundTest {
@Test
public void testEquals() {
Bound<Integer> bound = new Bound.FiniteBound<>(1, BoundType.OPEN_UPPER_BOUND);
Assert.assertEquals(bound, bound);
Assert.assertEquals(bound, new Bound.FiniteBound<>(1, BoundType.OPEN_UPPER_BOUND));
Assert.assertNotEquals(bound, new Bound.FiniteBound<>(2, BoundType.OPEN_UPPER_BOUND));
Assert.assertNotEquals(bound, new Bound.FiniteBound<>(1, BoundType.OPEN_LOWER_BOUND));
Assert.assertNotEquals(bound, null);
}
@Test
public void testHashCode() {
int hashCode = new Bound.FiniteBound<>(1, BoundType.OPEN_UPPER_BOUND).hashCode();
Assert.assertEquals(hashCode, new Bound.FiniteBound<>(1, BoundType.OPEN_UPPER_BOUND).hashCode());
Assert.assertNotEquals(hashCode, new Bound.FiniteBound<>(2, BoundType.OPEN_UPPER_BOUND).hashCode());
Assert.assertNotEquals(hashCode, new Bound.FiniteBound<>(1, BoundType.OPEN_LOWER_BOUND).hashCode());
}
@Test
public void testNoneLowerBound() {
Bound<Integer> bound = Bound.noneLowerBound();
// negate
Assert.assertEquals(bound, bound.negate());
// test
Assert.assertTrue(bound.test(Integer.MAX_VALUE));
// getType
Assert.assertEquals(BoundType.OPEN_LOWER_BOUND, bound.getType());
// getValue
Assert.assertNull(bound.getValue());
// toString
Assert.assertEquals("(" + "-\u221e", bound.descBound());
// compareTo
Assert.assertEquals(0, bound.compareTo(bound));
Assert.assertEquals(-1, bound.compareTo(Bound.atMost(1)));
Assert.assertEquals(BoundedRange.all(), bound.toRange());
Assert.assertEquals("{x | x > -\u221e}", bound.toString());
}
@Test
public void testNoneUpperBound() {
Bound<Integer> bound = Bound.noneUpperBound();
// negate
Assert.assertEquals(bound, bound.negate());
// test
Assert.assertTrue(bound.test(Integer.MAX_VALUE));
// getType
Assert.assertEquals(BoundType.OPEN_UPPER_BOUND, bound.getType());
// getValue
Assert.assertNull(bound.getValue());
// toString
Assert.assertEquals("+\u221e" + ")", bound.descBound());
// compareTo
Assert.assertEquals(0, bound.compareTo(bound));
Assert.assertEquals(1, bound.compareTo(Bound.atMost(1)));
Assert.assertEquals(BoundedRange.all(), bound.toRange());
Assert.assertEquals("{x | x < +\u221e}", bound.toString());
}
@Test
public void testGreatThan() {
// { x | x > 0}
Bound<Integer> bound = Bound.greaterThan(0);
// test
Assert.assertTrue(bound.test(1));
Assert.assertFalse(bound.test(0));
Assert.assertFalse(bound.test(-1));
// getType
Assert.assertEquals(BoundType.OPEN_LOWER_BOUND, bound.getType());
// getValue
Assert.assertEquals((Integer)0, bound.getValue());
// toString
Assert.assertEquals("(0", bound.descBound());
Assert.assertEquals("{x | x > 0}", bound.toString());
// compareTo
Assert.assertEquals(0, bound.compareTo(bound));
Assert.assertEquals(-1, bound.compareTo(Bound.noneUpperBound()));
Assert.assertEquals(1, bound.compareTo(Bound.atLeast(-1)));
Assert.assertEquals(-1, bound.compareTo(Bound.atLeast(2)));
Assert.assertEquals(1, bound.compareTo(Bound.lessThan(0)));
Assert.assertEquals(1, bound.compareTo(Bound.atMost(0)));
Assert.assertEquals(-1, bound.compareTo(Bound.atMost(2)));
Assert.assertEquals(1, bound.compareTo(Bound.noneLowerBound()));
// { x | x >= 0}
bound = bound.negate();
Assert.assertEquals((Integer)0, bound.getValue());
Assert.assertEquals(BoundType.CLOSE_UPPER_BOUND, bound.getType());
Assert.assertNotNull(bound.toRange());
}
@Test
public void testAtLeast() {
// { x | x >= 0}
Bound<Integer> bound = Bound.atLeast(0);
// test
Assert.assertTrue(bound.test(1));
Assert.assertTrue(bound.test(0));
Assert.assertFalse(bound.test(-1));
// getType
Assert.assertEquals(BoundType.CLOSE_LOWER_BOUND, bound.getType());
// getValue
Assert.assertEquals((Integer)0, bound.getValue());
// toString
Assert.assertEquals("[0", bound.descBound());
Assert.assertEquals("{x | x >= 0}", bound.toString());
// compareTo
Assert.assertEquals(0, bound.compareTo(bound));
Assert.assertEquals(-1, bound.compareTo(Bound.noneUpperBound()));
Assert.assertEquals(1, bound.compareTo(Bound.greaterThan(-1)));
Assert.assertEquals(-1, bound.compareTo(Bound.greaterThan(0)));
Assert.assertEquals(1, bound.compareTo(Bound.lessThan(0)));
Assert.assertEquals(1, bound.compareTo(Bound.atMost(0)));
Assert.assertEquals(-1, bound.compareTo(Bound.atMost(2)));
Assert.assertEquals(1, bound.compareTo(Bound.noneLowerBound()));
// { x | x < 0}
bound = bound.negate();
Assert.assertEquals((Integer)0, bound.getValue());
Assert.assertEquals(BoundType.OPEN_UPPER_BOUND, bound.getType());
Assert.assertNotNull(bound.toRange());
}
@Test
public void testLessThan() {
// { x | x < 0}
Bound<Integer> bound = Bound.lessThan(0);
// test
Assert.assertFalse(bound.test(1));
Assert.assertFalse(bound.test(0));
Assert.assertTrue(bound.test(-1));
// getType
Assert.assertEquals(BoundType.OPEN_UPPER_BOUND, bound.getType());
// getValue
Assert.assertEquals((Integer)0, bound.getValue());
// toString
Assert.assertEquals("0)", bound.descBound());
Assert.assertEquals("{x | x < 0}", bound.toString());
// compareTo
Assert.assertEquals(0, bound.compareTo(bound));
Assert.assertEquals(-1, bound.compareTo(Bound.noneUpperBound()));
Assert.assertEquals(1, bound.compareTo(Bound.greaterThan(-1)));
Assert.assertEquals(-1, bound.compareTo(Bound.greaterThan(0)));
Assert.assertEquals(1, bound.compareTo(Bound.lessThan(-1)));
Assert.assertEquals(-1, bound.compareTo(Bound.atMost(0)));
Assert.assertEquals(1, bound.compareTo(Bound.atMost(-1)));
Assert.assertEquals(1, bound.compareTo(Bound.noneLowerBound()));
// { x | x >= 0}
bound = bound.negate();
Assert.assertEquals((Integer)0, bound.getValue());
Assert.assertEquals(BoundType.CLOSE_LOWER_BOUND, bound.getType());
Assert.assertNotNull(bound.toRange());
}
@Test
public void testAtMost() {
// { x | x <= 0}
Bound<Integer> bound = Bound.atMost(0);
// test
Assert.assertFalse(bound.test(1));
Assert.assertTrue(bound.test(0));
Assert.assertTrue(bound.test(-1));
// getType
Assert.assertEquals(BoundType.CLOSE_UPPER_BOUND, bound.getType());
// getValue
Assert.assertEquals((Integer)0, bound.getValue());
// toString
Assert.assertEquals("0]", bound.descBound());
Assert.assertEquals("{x | x <= 0}", bound.toString());
// compareTo
Assert.assertEquals(0, bound.compareTo(bound));
Assert.assertEquals(-1, bound.compareTo(Bound.noneUpperBound()));
Assert.assertEquals(1, bound.compareTo(Bound.greaterThan(-1)));
Assert.assertEquals(-1, bound.compareTo(Bound.greaterThan(0)));
Assert.assertEquals(1, bound.compareTo(Bound.atMost(-1)));
Assert.assertEquals(1, bound.compareTo(Bound.lessThan(0)));
Assert.assertEquals(1, bound.compareTo(Bound.lessThan(-1)));
Assert.assertEquals(1, bound.compareTo(Bound.noneLowerBound()));
// { x | x > 0}
bound = bound.negate();
Assert.assertEquals((Integer)0, bound.getValue());
Assert.assertEquals(BoundType.OPEN_LOWER_BOUND, bound.getType());
Assert.assertNotNull(bound.toRange());
}
}

View File

@ -0,0 +1,83 @@
package cn.hutool.core.lang.range;
import org.junit.Assert;
import org.junit.Test;
/**
* test for {@link BoundType}
*/
public class BoundTypeTest {
@Test
public void testIsDislocated() {
Assert.assertTrue(BoundType.CLOSE_LOWER_BOUND.isDislocated(BoundType.CLOSE_UPPER_BOUND));
Assert.assertTrue(BoundType.CLOSE_LOWER_BOUND.isDislocated(BoundType.OPEN_UPPER_BOUND));
Assert.assertFalse(BoundType.CLOSE_LOWER_BOUND.isDislocated(BoundType.CLOSE_LOWER_BOUND));
Assert.assertFalse(BoundType.CLOSE_LOWER_BOUND.isDislocated(BoundType.OPEN_LOWER_BOUND));
}
@Test
public void testIsLowerBound() {
Assert.assertFalse(BoundType.CLOSE_UPPER_BOUND.isLowerBound());
Assert.assertFalse(BoundType.OPEN_UPPER_BOUND.isLowerBound());
Assert.assertTrue(BoundType.CLOSE_LOWER_BOUND.isLowerBound());
Assert.assertTrue(BoundType.OPEN_LOWER_BOUND.isLowerBound());
}
@Test
public void testIsUpperBound() {
Assert.assertTrue(BoundType.CLOSE_UPPER_BOUND.isUpperBound());
Assert.assertTrue(BoundType.OPEN_UPPER_BOUND.isUpperBound());
Assert.assertFalse(BoundType.CLOSE_LOWER_BOUND.isUpperBound());
Assert.assertFalse(BoundType.OPEN_LOWER_BOUND.isUpperBound());
}
@Test
public void testIsOpen() {
Assert.assertFalse(BoundType.CLOSE_UPPER_BOUND.isOpen());
Assert.assertTrue(BoundType.OPEN_UPPER_BOUND.isOpen());
Assert.assertFalse(BoundType.CLOSE_LOWER_BOUND.isOpen());
Assert.assertTrue(BoundType.OPEN_LOWER_BOUND.isOpen());
}
@Test
public void testIsClose() {
Assert.assertTrue(BoundType.CLOSE_UPPER_BOUND.isClose());
Assert.assertFalse(BoundType.OPEN_UPPER_BOUND.isClose());
Assert.assertTrue(BoundType.CLOSE_LOWER_BOUND.isClose());
Assert.assertFalse(BoundType.OPEN_LOWER_BOUND.isClose());
}
@Test
public void testNegate() {
Assert.assertEquals(BoundType.CLOSE_UPPER_BOUND, BoundType.OPEN_LOWER_BOUND.negate());
Assert.assertEquals(BoundType.OPEN_UPPER_BOUND, BoundType.CLOSE_LOWER_BOUND.negate());
Assert.assertEquals(BoundType.OPEN_LOWER_BOUND, BoundType.CLOSE_UPPER_BOUND.negate());
Assert.assertEquals(BoundType.CLOSE_LOWER_BOUND, BoundType.OPEN_UPPER_BOUND.negate());
}
@Test
public void testGetSymbol() {
Assert.assertEquals("]", BoundType.CLOSE_UPPER_BOUND.getSymbol());
Assert.assertEquals(")", BoundType.OPEN_UPPER_BOUND.getSymbol());
Assert.assertEquals("[", BoundType.CLOSE_LOWER_BOUND.getSymbol());
Assert.assertEquals("(", BoundType.OPEN_LOWER_BOUND.getSymbol());
}
@Test
public void testGetCode() {
Assert.assertEquals(2, BoundType.CLOSE_UPPER_BOUND.getCode());
Assert.assertEquals(1, BoundType.OPEN_UPPER_BOUND.getCode());
Assert.assertEquals(-1, BoundType.OPEN_LOWER_BOUND.getCode());
Assert.assertEquals(-2, BoundType.CLOSE_LOWER_BOUND.getCode());
}
@Test
public void testGetOperator() {
Assert.assertEquals("<=", BoundType.CLOSE_UPPER_BOUND.getOperator());
Assert.assertEquals("<", BoundType.OPEN_UPPER_BOUND.getOperator());
Assert.assertEquals(">", BoundType.OPEN_LOWER_BOUND.getOperator());
Assert.assertEquals(">=", BoundType.CLOSE_LOWER_BOUND.getOperator());
}
}

View File

@ -0,0 +1,283 @@
package cn.hutool.core.lang.range;
import org.junit.Assert;
import org.junit.Test;
/**
* test for {@link BoundedRange}
*/
public class BoundedRangeTest {
@Test
public void testEquals() {
BoundedRange<Integer> range = new BoundedRange<>(
Bound.greaterThan(0), Bound.lessThan(10)
);
Assert.assertEquals(range, range);
Assert.assertNotEquals(range, null);
Assert.assertEquals(range, new BoundedRange<>(
Bound.greaterThan(0), Bound.lessThan(10)
));
Assert.assertNotEquals(range, new BoundedRange<>(
Bound.greaterThan(1), Bound.lessThan(10)
));
}
@Test
public void testHashCode() {
int hasCode = new BoundedRange<>(
Bound.greaterThan(0), Bound.lessThan(10)
).hashCode();
Assert.assertEquals(hasCode, new BoundedRange<>(
Bound.greaterThan(0), Bound.lessThan(10)
).hashCode());
Assert.assertNotEquals(hasCode, new BoundedRange<>(
Bound.greaterThan(1), Bound.lessThan(10)
).hashCode());
}
@Test
public void testAll() {
BoundedRange<Integer> range = BoundedRange.all();
Assert.assertEquals("(-∞, +∞)", range.toString());
// getBound
Assert.assertFalse(range.hasLowerBound());
Assert.assertFalse(range.hasUpperBound());
Assert.assertNull(range.getLowerBoundValue());
Assert.assertNull(range.getUpperBoundValue());
// test
Assert.assertFalse(range.isEmpty());
Assert.assertTrue(range.test(Integer.MAX_VALUE));
Assert.assertTrue(range.test(Integer.MIN_VALUE));
// isXXX
Assert.assertFalse(range.isDisjoint(BoundedRange.open(0, 5)));
Assert.assertTrue(range.isEquals(range));
Assert.assertFalse(range.isEquals(BoundedRange.open(0, 5)));
Assert.assertTrue(range.isIntersected(BoundedRange.open(0, 5)));
Assert.assertTrue(range.isIntersected(range));
Assert.assertFalse(range.isSubset(BoundedRange.open(0, 5)));
Assert.assertTrue(range.isSubset(range));
Assert.assertFalse(range.isProperSubset(BoundedRange.open(0, 5)));
Assert.assertFalse(range.isProperSubset(range));
Assert.assertTrue(range.isSuperset(BoundedRange.open(0, 5)));
Assert.assertTrue(range.isSuperset(range));
Assert.assertTrue(range.isProperSuperset(BoundedRange.open(0, 5)));
Assert.assertFalse(range.isProperSuperset(range));
// operate
Assert.assertEquals(range, range.unionIfIntersected(BoundedRange.open(0, 5)));
Assert.assertEquals(range, range.span(BoundedRange.open(0, 5)));
Assert.assertNull(range.gap(BoundedRange.open(0, 5)));
Assert.assertEquals(range, range.intersection(range));
Assert.assertEquals(BoundedRange.open(0, 5), range.intersection(BoundedRange.open(0, 5)));
// sub
Assert.assertEquals("(0, +∞)", range.subGreatThan(0).toString());
Assert.assertEquals("[0, +∞)", range.subAtLeast(0).toString());
Assert.assertEquals("(-∞, 0)", range.subLessThan(0).toString());
Assert.assertEquals("(-∞, 0]", range.subAtMost(0).toString());
}
@Test
public void testOpen() {
BoundedRange<Integer> range = BoundedRange.open(0, 5);
Assert.assertEquals("(0, 5)", range.toString());
// getBound
Assert.assertTrue(range.hasLowerBound());
Assert.assertTrue(range.hasUpperBound());
Assert.assertEquals((Integer)0, range.getLowerBoundValue());
Assert.assertEquals((Integer)5, range.getUpperBoundValue());
// test
Assert.assertFalse(range.isEmpty());
Assert.assertFalse(range.test(6));
Assert.assertFalse(range.test(5));
Assert.assertTrue(range.test(3));
Assert.assertFalse(range.test(0));
Assert.assertFalse(range.test(-1));
// isXXX
Assert.assertTrue(range.isEquals(range));
Assert.assertTrue(range.isDisjoint(BoundedRange.open(-5, 0))); // (-5, 0)
Assert.assertTrue(range.isDisjoint(BoundedRange.close(-5, 0))); // [-5, 0]
Assert.assertTrue(range.isIntersected(BoundedRange.close(-5, 1))); // [-5, 1]
Assert.assertTrue(range.isSubset(BoundedRange.close(0, 5))); // [0, 5]
Assert.assertTrue(range.isProperSubset(BoundedRange.close(0, 5))); // [0, 5]
Assert.assertFalse(range.isSuperset(BoundedRange.close(0, 5))); // [0, 5]
Assert.assertFalse(range.isProperSuperset(BoundedRange.close(0, 5))); // [0, 5]
// operate
Assert.assertEquals("(0, 10]", range.unionIfIntersected(BoundedRange.close(4, 10)).toString());
Assert.assertEquals("(0, 10)", range.span(BoundedRange.open(9, 10)).toString());
Assert.assertEquals("(-2, 0]", range.gap(BoundedRange.close(-10, -2)).toString());
Assert.assertEquals("(3, 5)", range.intersection(BoundedRange.open(3, 10)).toString());
// sub
Assert.assertEquals("(3, 5)", range.subGreatThan(3).toString());
Assert.assertEquals("[3, 5)", range.subAtLeast(3).toString());
Assert.assertEquals("(0, 3)", range.subLessThan(3).toString());
Assert.assertEquals("(0, 3]", range.subAtMost(3).toString());
}
@Test
public void testClose() {
BoundedRange<Integer> range = BoundedRange.close(0, 5);
Assert.assertEquals("[0, 5]", range.toString());
// getBound
Assert.assertTrue(range.hasLowerBound());
Assert.assertTrue(range.hasUpperBound());
Assert.assertEquals((Integer)0, range.getLowerBoundValue());
Assert.assertEquals((Integer)5, range.getUpperBoundValue());
// test
Assert.assertFalse(range.isEmpty());
Assert.assertFalse(range.test(6));
Assert.assertTrue(range.test(5));
Assert.assertTrue(range.test(0));
Assert.assertFalse(range.test(-1));
// isXXX
Assert.assertTrue(range.isEquals(range));
Assert.assertTrue(range.isDisjoint(BoundedRange.open(-5, 0))); // (-5, 0)
Assert.assertTrue(range.isDisjoint(BoundedRange.close(-5, 0))); // [-5, 0]
Assert.assertTrue(range.isIntersected(BoundedRange.open(-5, 1))); // [-5, 1]
Assert.assertFalse(range.isSubset(BoundedRange.open(0, 5))); // (0, 5)
Assert.assertFalse(range.isProperSubset(BoundedRange.open(0, 5))); // (0, 5)
Assert.assertTrue(range.isSuperset(BoundedRange.open(0, 5))); // (0, 5)
Assert.assertTrue(range.isProperSuperset(BoundedRange.open(0, 5))); // (0, 5)
// operate
Assert.assertEquals("[0, 5]", range.unionIfIntersected(BoundedRange.open(5, 10)).toString());
Assert.assertEquals("[0, 10]", range.unionIfIntersected(BoundedRange.close(4, 10)).toString());
Assert.assertEquals("[0, 10)", range.span(BoundedRange.open(9, 10)).toString());
Assert.assertEquals("(-2, 0)", range.gap(BoundedRange.close(-10, -2)).toString());
Assert.assertEquals("(3, 5]", range.intersection(BoundedRange.open(3, 10)).toString());
Assert.assertNull(range.intersection(BoundedRange.open(5, 10)));
// sub
Assert.assertEquals("(3, 5]", range.subGreatThan(3).toString());
Assert.assertEquals("[3, 5]", range.subAtLeast(3).toString());
Assert.assertEquals("[0, 3)", range.subLessThan(3).toString());
Assert.assertEquals("[0, 3]", range.subAtMost(3).toString());
}
@Test
public void testOpenClose() {
BoundedRange<Integer> range = BoundedRange.openClose(0, 5);
Assert.assertEquals("(0, 5]", range.toString());
// getBound
Assert.assertTrue(range.hasLowerBound());
Assert.assertTrue(range.hasUpperBound());
Assert.assertEquals((Integer)0, range.getLowerBoundValue());
Assert.assertEquals((Integer)5, range.getUpperBoundValue());
// test
Assert.assertFalse(range.isEmpty());
Assert.assertFalse(range.test(6));
Assert.assertTrue(range.test(5));
Assert.assertFalse(range.test(0));
Assert.assertFalse(range.test(-1));
}
@Test
public void testCloseOpen() {
BoundedRange<Integer> range = BoundedRange.closeOpen(0, 5);
Assert.assertEquals("[0, 5)", range.toString());
// getBound
Assert.assertTrue(range.hasLowerBound());
Assert.assertTrue(range.hasUpperBound());
Assert.assertEquals((Integer)0, range.getLowerBoundValue());
Assert.assertEquals((Integer)5, range.getUpperBoundValue());
// test
Assert.assertFalse(range.isEmpty());
Assert.assertFalse(range.test(6));
Assert.assertFalse(range.test(5));
Assert.assertTrue(range.test(0));
Assert.assertFalse(range.test(-1));
}
@Test
public void testGreatThan() {
BoundedRange<Integer> range = BoundedRange.greaterThan(0);
Assert.assertEquals("(0, +∞)", range.toString());
// getBound
Assert.assertTrue(range.hasLowerBound());
Assert.assertFalse(range.hasUpperBound());
Assert.assertEquals((Integer)0, range.getLowerBoundValue());
Assert.assertNull(range.getUpperBoundValue());
// test
Assert.assertFalse(range.isEmpty());
Assert.assertTrue(range.test(6));
Assert.assertTrue(range.test(5));
Assert.assertFalse(range.test(0));
Assert.assertFalse(range.test(-1));
}
@Test
public void testAtLeast() {
BoundedRange<Integer> range = BoundedRange.atLeast(0);
Assert.assertEquals("[0, +∞)", range.toString());
// getBound
Assert.assertTrue(range.hasLowerBound());
Assert.assertFalse(range.hasUpperBound());
Assert.assertEquals((Integer)0, range.getLowerBoundValue());
Assert.assertNull(range.getUpperBoundValue());
// test
Assert.assertFalse(range.isEmpty());
Assert.assertTrue(range.test(6));
Assert.assertTrue(range.test(5));
Assert.assertTrue(range.test(0));
Assert.assertFalse(range.test(-1));
}
@Test
public void testLessThan() {
BoundedRange<Integer> range = BoundedRange.lessThan(5);
Assert.assertEquals("(-∞, 5)", range.toString());
// getBound
Assert.assertTrue(range.hasUpperBound());
Assert.assertFalse(range.hasLowerBound());
Assert.assertEquals((Integer)5, range.getUpperBoundValue());
Assert.assertNull(range.getLowerBoundValue());
// test
Assert.assertFalse(range.isEmpty());
Assert.assertFalse(range.test(6));
Assert.assertFalse(range.test(5));
Assert.assertTrue(range.test(0));
Assert.assertTrue(range.test(-1));
}
@Test
public void testAtMost() {
BoundedRange<Integer> range = BoundedRange.atMost(5);
Assert.assertEquals("(-∞, 5]", range.toString());
// getBound
Assert.assertTrue(range.hasUpperBound());
Assert.assertFalse(range.hasLowerBound());
Assert.assertEquals((Integer)5, range.getUpperBoundValue());
Assert.assertNull(range.getLowerBoundValue());
// test
Assert.assertFalse(range.isEmpty());
Assert.assertFalse(range.test(6));
Assert.assertTrue(range.test(5));
Assert.assertTrue(range.test(0));
Assert.assertTrue(range.test(-1));
}
}