org.nerd4j.utils.math.Interval Maven / Gradle / Ivy
/*-
* #%L
* Nerd4j Utils
* %%
* Copyright (C) 2011 - 2020 Nerd4j
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
package org.nerd4j.utils.math;
import org.nerd4j.utils.lang.*;
import java.util.NoSuchElementException;
import static org.nerd4j.utils.math.CU.*;
/**
* In mathematics, given a set {@code S} and a total order relation {@code <=}
* over the elements of {@code S}, we define an interval {@code I} as a
* subset of {@code S} such that: given {@code x,y in I such that x <= y} we have
* that {@code for each s in S} if {@code x <= s <= y} then {@code s belongs to I}.
*
*
* An interval can be empty, it can have a lower bound value called
* {@code inf}, it can have an upper bound value called {@code sup},
* and it can also be unbounded. The upper and lower bounds can be included
* in the interval, in this case we say that the interval is closed, or they may
* be excluded from the interval, in this case we say that the interval is open.
*
*
* This definition can be applied to numbers but it can also be applied to
* dates, strings, pairs and any other comparable value.
*
*
* This class provides a Java representation of an interval as described above.
* It allows some operations on intervals like: union, intersection, subtraction and
* checks like if two intervals overlaps, if are disjoint, if one includes another, if
* a given value belongs to the interval, etc.
*
*
* An interval is immutable. Operations on intervals will create new intervals.
* If two intervals are disjoint the method {@link #unify(Interval)} will throw
* a {@link NoSuchIntervalException}. For unbounded intervals the methods
* {@link #getInf()} and {@link #getSup()} may throw a {@link NoSuchElementException}.
*
*
* @param type of comparable elements of the interval.
*
* @author Massimo Coluzzi
* @since 2.0.0
*/
public class Interval>
implements Emptily
{
/** Singleton instance of the "empty set" interval. */
private static final Interval EMPTY = new Interval();
/** Singleton instance of an unbounded interval. */
private static final Interval UNBOUNDED
= new Interval( IntervalLimit.unboundedInf(), IntervalLimit.unboundedSup() );
/** The inferior limit of the interval. */
private final IntervalLimit inf;
/** The superior limit of the interval. */
private final IntervalLimit sup;
/**
* Default constructor.
*
* This constructor is intended to be used
* by reflection during de-serialization.
*
* To create a new interval
* use factory methods.
*/
protected Interval()
{
super();
this.inf = null;
this.sup = null;
}
/**
* Constructor with parameters.
*
* This constructor is intended to be used
* by extending classes only.
*
* To create a new interval use
* the factory methods.
*
* @param inf the inferior limit of the interval.
* @param sup the superior limit of the interval.
*/
protected Interval( IntervalLimit inf, IntervalLimit sup )
{
super();
this.inf = Require.nonNull( inf, "The inferior limit is mandatory" );
this.sup = Require.nonNull( sup, "The superior limit is mandatory" );
Require.toHold( inf.isInf(), "The provided 'inf' param is not an inferior limit" );
Require.toHold( sup.isSup(), "The provided 'sup' param is not a superior limit" );
if( ! inf.isUnbounded() && ! sup.isUnbounded() )
Require.toHold( le(inf.value,sup.value), "The inferior limit cannot be greater than the superior limit" );
}
/* ***************** */
/* FACTORY METHODS */
/* ***************** */
/**
* Returns the singleton instance of the "empty set" {@link Interval}.
*
* The empty set is considered to be an empty interval with undefined
* superior and inferior limits.
*
* Invoking {@link #getInf()} or {@link #getSup()} on this instance will
* throw a {@link NoSuchElementException}.
*
* @param type of the elements in the interval.
* @return singleton instance of an empty {@link Interval}.
*/
public static > Interval empty()
{
return (Interval) EMPTY;
}
/**
* Returns the singleton instance of an unbounded {@link Interval}.
*
* Superior and inferior limits are not defined for an unbounded
* interval so, invoking {@link #getInf()} or {@link #getSup()}
* on this instance, will throw a {@link NoSuchElementException}.
*
* @param type of the elements in the interval.
* @return singleton instance of an unbounded {@link Interval}.
*/
public static > Interval unbounded()
{
return (Interval) UNBOUNDED;
}
/**
* Creates a new {@link Interval} with a closed inferior
* limit and an unbounded superior limit.
*
* Invoking {@link #getSup()} on this instance
* will throw a {@link NoSuchElementException}.
*
* @param type of the elements in the interval.
* @param value the inferior limit.
* @return new instance of {@link Interval}.
* @throws RequirementFailure if limits are inconsistent.
*/
public static > Interval closedInf( T value )
{
return new Interval<>(
IntervalLimit.closedInf( value ),
IntervalLimit.unboundedSup()
);
}
/**
* Creates a new {@link Interval} with a open inferior
* limit and an unbounded superior limit.
*
* Invoking {@link #getSup()} on this instance
* will throw a {@link NoSuchElementException}.
*
* @param type of the elements in the interval.
* @param value the inferior limit.
* @return new instance of {@link Interval}.
* @throws RequirementFailure if limits are inconsistent.
*/
public static > Interval openInf( T value )
{
return new Interval<>(
IntervalLimit.openInf( value ),
IntervalLimit.unboundedSup()
);
}
/**
* Creates a new {@link Interval} with a closed superior
* limit and an unbounded inferior limit.
*
* Invoking {@link #getInf()} on this instance
* will throw a {@link NoSuchElementException}.
*
* @param type of the elements in the interval.
* @param value the superior limit.
* @return new instance of {@link Interval}.
* @throws RequirementFailure if limits are inconsistent.
*/
public static > Interval closedSup( T value )
{
return new Interval<>(
IntervalLimit.unboundedInf(),
IntervalLimit.closedSup( value )
);
}
/**
* Creates a new {@link Interval} with a open superior
* limit and an unbounded inferior limit.
*
* Invoking {@link #getInf()} on this instance
* will throw a {@link NoSuchElementException}.
*
* @param type of the elements in the interval.
* @param value the superior limit.
* @return new instance of {@link Interval}.
* @throws RequirementFailure if limits are inconsistent.
*/
public static > Interval openSup( T value )
{
return new Interval<>(
IntervalLimit.unboundedInf(),
IntervalLimit.openSup( value )
);
}
/**
* Creates a new closed {@link Interval} with the given limits.
*
* @param type of the elements in the interval.
* @param inf the inferior limit.
* @param sup the superior limit.
* @return new instance of {@link Interval}.
* @throws RequirementFailure if limits are inconsistent.
*/
public static > Interval closed( T inf, T sup )
{
return new Interval<>(
IntervalLimit.closedInf( inf ),
IntervalLimit.closedSup( sup )
);
}
/**
* Creates a new open {@link Interval} with the given limits.
*
* @param type of the elements in the interval.
* @param inf the inferior limit.
* @param sup the superior limit.
* @return new instance of an open {@link Interval}.
* @throws RequirementFailure if limits are inconsistent.
*/
public static > Interval open( T inf, T sup )
{
return new Interval<>(
IntervalLimit.openInf( inf ),
IntervalLimit.openSup( sup )
);
}
/**
* Creates a new {@link Interval} with a closed inferior limit
* and an open superior limit.
*
* @param type of the elements in the interval.
* @param inf the inferior limit.
* @param sup the superior limit.
* @return new instance of {@link Interval}.
* @throws RequirementFailure if limits are inconsistent.
*/
public static > Interval closedOpen( T inf, T sup )
{
return new Interval<>(
IntervalLimit.closedInf( inf ),
IntervalLimit.openSup( sup )
);
}
/**
* Creates a new {@link Interval} with an open inferior limit
* and a closed superior limit.
*
* @param type of the elements in the interval.
* @param inf the inferior limit.
* @param sup the superior limit.
* @return new instance of {@link Interval}.
* @throws RequirementFailure if limits are inconsistent.
*/
public static > Interval openClosed( T inf, T sup )
{
return new Interval<>(
IntervalLimit.openInf( inf ),
IntervalLimit.closedSup( sup )
);
}
/* ******************* */
/* INTERFACE METHODS */
/* ******************* */
/**
* {@inheritDoc}
*/
@Override
public boolean isEmpty()
{
/*
* An interval is considered to be empty
* if it is the empty set or if it is a
* single point open interval in the form
* (x,x), (x,x] or [x,x).
*/
if( isEmptySet() )
return true;
if( eq(inf.value,sup.value) )
return inf.isOpen() || sup.isOpen();
return false;
}
/* **************** */
/* PUBLIC METHODS */
/* **************** */
/**
* An interval is considered to be empty
* if it is the empty set or if it is a
* single point open interval in the form
* {@code (x,x)}.
*
* The method {@link #isEmpty()} returns
* {@code true} in both cases. This method
* returns {@code true} only if this interval
* is the empty set.
*
* @return {@code true} if this interval is the empty set.
*/
public boolean isEmptySet()
{
/*
* The empty interval is the only one
* with undefined inferior and superior
* limits.
*/
return inf == null && sup == null;
}
/**
* Tells if both the inferior limit and
* the superior limit are unbounded.
*
* @return {@code true} if both limit are unbounded.
*/
public boolean isUnbounded()
{
return isUnboundedInf() && isUnboundedSup();
}
/**
* Tells if the inferior limit is unbounded.
*
* @return {@code true} if the inferior limit is unbounded.
*/
public boolean isUnboundedInf()
{
return inf != null && inf.isUnbounded();
}
/**
* Tells if the superior limit is unbounded.
*
* @return {@code true} if the superior limit is unbounded.
*/
public boolean isUnboundedSup()
{
return sup != null && sup.isUnbounded();
}
/**
* Tells if both the inferior limit and
* the superior limit are closed.
*
* @return {@code true} if both limit are closed.
*/
public boolean isClosed()
{
return isClosedInf() && isClosedSup();
}
/**
* Tells if the inferior limit is closed.
*
* @return {@code true} if the inferior limit is closed.
*/
public boolean isClosedInf()
{
return inf != null && inf.isClosed();
}
/**
* Tells if the superior limit is closed.
*
* @return {@code true} if the superior limit is closed.
*/
public boolean isClosedSup()
{
return sup != null && sup.isClosed();
}
/**
* Tells if both the inferior limit and
* the superior limit are open.
*
* @return {@code true} if both limit are open.
*/
public boolean isOpen()
{
return isOpenInf() && isOpenSup();
}
/**
* Tells if the inferior limit is open.
*
* @return {@code true} if the inferior limit is open.
*/
public boolean isOpenInf()
{
return inf != null && inf.isOpen();
}
/**
* Tells if the superior limit is open.
*
* @return {@code true} if the superior limit is open.
*/
public boolean isOpenSup()
{
return sup != null && sup.isOpen();
}
/**
* Returns the value of the inferior limit
* if it is defined. If the inferior limit
* is unbounded or if this interval is the
* empty set a {@link NoSuchElementException}
* will be thrown.
*
* @return the inferior limit if defined.
* @throws NoSuchElementException if the inferior
* limit is unbounded or this is the empty set.
*/
public T getInf()
{
if( inf == null || inf.isUnbounded() )
throw new NoSuchElementException( "The inferior limit is undefined for " + this );
return inf.value;
}
/**
* Returns the value of the superior limit
* if it is defined. If the superior limit
* is unbounded or if this interval is the
* empty set a {@link NoSuchElementException}
* will be thrown.
*
* @return the superior limit if defined.
* @throws NoSuchElementException if the superior
* limit is unbounded or this is the empty set.
*/
public T getSup()
{
if( sup == null || sup.isUnbounded() )
throw new NoSuchElementException( "The superior limit is undefined for " + this );
return sup.value;
}
/**
* Tells if this interval contains the given value.
*
* @param value the value to check.
* @return {@code true} it the value belongs to the interval.
* @throws RequirementFailure if the provided value is {@code null}.
*/
public boolean contains( T value )
{
Require.nonNull( value, "The value to check is mandatory" );
return inf != null && sup != null &&
inf.contains( value ) &&
sup.contains( value );
}
/**
* Tells if this interval includes the given one.
*
* An interval A is included into another interval B
* if any element of A belongs also to B.
*
* The empty set is included into any other interval.
*
* @param other the interval to check.
* @return {@code true} if the other interval is included in this one.
* @throws RequirementFailure if the other interval is {@code null}.
*/
public boolean includes( Interval other )
{
Require.nonNull( other, "The interval to check is mandatory" );
/*
* Every interval includes the empty interval,
* even the empty interval itself.
*/
if( other.isEmptySet() )
return true;
/*
* If the other interval is not the empty set
* we check this interval to be not the empty
* set and we check the limits of the other
* interval to lay within the limits of this one.
*/
return ! this.isEmptySet()
&& le( this.inf, other.inf )
&& ge( this.sup, other.sup );
}
/**
* Tells if this interval overlaps the given one.
*
* Two intervals overlap if they have at least one
* element in common and if they are not including
* each other. In mathematical notation: two intervals
* {@code A} and {@code B} overlap if {@code A \u2229 B \u2260 \u2205}
* and {@code A \u2288 B} and {@code B \u2288 A}.
* Since there must exist at least one element in common
* an empty interval cannot overlap with any other interval.
*
* @param other the interval to check.
* @return {@code true} if this interval overlaps the other one.
* @throws RequirementFailure if the other interval is {@code null}.
*/
public boolean overlaps( Interval other )
{
Require.nonNull( other, "The interval to check is mandatory" );
/*
* If the one interval includes the other
* they are not considered to be overlapping.
* If one of the of the two intervals is empty
* is included into the other so the emptiness
* check is already made by the method includes.
*/
if( this.includes(other) || other.includes(this) )
return false;
/*
* Neither of the two intervals is empty
* and neither of the two intervals includes
* the other so, for both of them, inf and sup
* are not null and not both equal.
* We need to check if either inf or sup of one
* interval belong to the other.
*/
return ( le(this.inf,other.inf) && ge(this.sup,other.inf) )
|| ( le(this.inf,other.sup) && ge(this.sup,other.sup) );
}
/**
* Tells if this interval is disjoint frp, the given one.
*
* Two intervals are said to be disjoint if they have
* no elements in common. In mathematical notation:
* two intervals {@code A} and {@code B} are disjoint
* if {@code A \u2229 B = \u2205}.
*
* Since two intervals are disjoint if they have no elements
* in common then any empty interval is disjoint from any other
* interval including itself.
*
* @param other the interval to check.
* @return {@code true} if this interval overlaps the other one.
* @throws RequirementFailure if the other interval is {@code null}.
*/
public boolean isDisjointFrom( Interval other )
{
Require.nonNull( other, "The interval to check is mandatory" );
/*
* The empty set is disjoint from any other interval.
*/
if( this.isEmpty() || other.isEmpty() )
return true;
/*
* If the intervals are unbounded cannot be disjoint.
* Otherwise we need to check that they do not overlap.
*/
if( ! this.sup.isUnbounded() &&
! other.inf.isUnbounded() &&
lt(this.sup, other.inf) )
return true;
return ! other.sup.isUnbounded() &&
! this.inf.isUnbounded() &&
lt( other.sup, this.inf );
}
/**
* Tells if this interval is consecutive to the given one.
*
* Two intervals are considered to be consecutive if they
* share one limit and at least for one of the intervals
* the given limit is closed. {@code (x,y),[y,z]}, {@code [x,y],(y,z]}
* and {@code (x,y],[y,z)} are all consecutive intervals
* while {@code [x,y),(y,z]} are not consecutive.
*
* The empty set is consecutive to any other interval.
*
* @param other the interval to check.
* @return {@code} if this is consecutive to other.
* @throws RequirementFailure if the other interval is {@code null}.
*/
public boolean isConsecutiveTo( Interval other )
{
Require.nonNull( other, "The interval to check is mandatory" );
/*
* The empty set is consecutive to any
* other interval.
*/
if( this.isEmptySet() || other.isEmptySet() )
return true;
/*
* We check if this.sup coincides
* with other.inf.
*/
if( ! this.sup.isUnbounded() && ! other.inf.isUnbounded()
&& eq( this.sup.value, other.inf.value ) )
return this.sup.isClosed() || other.inf.isClosed();
/*
* We check if this.inf coincides
* with other.sup.
*/
if( ! this.inf.isUnbounded() && ! other.sup.isUnbounded()
&& eq( this.inf.value, other.sup.value ) )
return this.inf.isClosed() || other.sup.isClosed();
/* Otherwise they are not consecutive. */
return false;
}
/**
* This is a strict version of the {@link #isConsecutiveTo(Interval)}
* method where a further check is made to ensure that the two
* intervals are not overlapping.
*
* In this case {@code (x,y),[y,z]} and {@code [x,y],(y,z]}
* are still considered to be consecutive while
* {@code (x,y],[y,z)} are not.
*
* The empty set is strictly consecutive to any other interval.
*
* @param other the interval to check.
* @return {@code} if this is consecutive to other.
* @throws RequirementFailure if the other interval is {@code null}.
*/
public boolean isStrictlyConsecutiveTo( Interval other )
{
Require.nonNull( other, "The interval to check is mandatory" );
/*
* The empty set is consecutive to any
* other interval.
*/
if( this.isEmptySet() || other.isEmptySet() )
return true;
/*
* We check if this.sup coincides
* with other.inf.
*/
if( ! this.sup.isUnbounded() && ! other.inf.isUnbounded()
&& eq( this.sup.value, other.inf.value ) )
return ( this.sup.isClosed() && other.inf.isOpen() ) ||
( this.sup.isOpen() && other.inf.isClosed() );
/*
* We check if this.inf coincides
* with other.sup.
*/
if( ! this.inf.isUnbounded() && ! other.sup.isUnbounded()
&& eq( this.inf.value, other.sup.value ) )
return ( this.inf.isClosed() && other.sup.isOpen() ) ||
( this.inf.isOpen() && other.sup.isClosed() );
/* Otherwise they are not consecutive. */
return false;
}
/**
* Tells if this interval can be unified with the given one.
*
* When we work with sets, the operation of union is always
* possible. If we unify two sets, the result is still a set.
* When we work with intervals this is not true.
*
* By definition, a totally ordered set {@code I} is an interval
* if: for each {@code x} such that {@code inf(I) <= x <= sup(I)}
* we have that {@code x} belongs to {@code I}.
*
* If we unify {@code [0,5]} and {@code [15,20]} the result is
* not an interval because, for example the value {@code 10}, does
* not belong to {@code [0,5]\u222a[15,20]}
*
* Two intervals can be unified if:
*
* - they are equal
* - they overlap
* - they are consecutive
*
*
* A special case is given by the empty intervals. While the empty
* set can be unified to any other interval, an empty interval can
* be unified with another interval only if its limits lay within
* the limits of the other interval. For example {@code [0,10]} and
* {@code (5,5)} can be unified while {@code [5,10]} and {@code (0,0)}
* cannot.
*
* If this method returns {@code true} the method {@link #unify(Interval)}
* will return a new {@link Interval} that represents the union of this
* interval with the given one. Otherwise, if this method returns {@code false}
* the method {@link #unify(Interval)} will throw a {@link NoSuchIntervalException}.
*
* @param other the interval to check.
* @return {@code true} if this interval can be unified with the other one.
* @throws RequirementFailure if the other interval is {@code null}.
*/
public boolean canUnify( Interval other )
{
Require.nonNull( other, "The interval to check is mandatory" );
/*
* If an interval includes the other they can be unified.
* This check includes also the cases where the intervals
* are empty end the intervals are equal.
*/
if( this.includes(other) || other.includes(this) )
return true;
/*
* This check catches three cases:
* 1. If two non empty intervals are equal.
* 2. If two non empty intervals overlap.
* 3. If the limits of an empty interval
* lay within the limits of the other one.
*/
if( ( le(this.inf,other.inf) && ge(this.sup,other.inf) ) ||
( le(this.inf,other.sup) && ge(this.sup,other.sup) ) )
return true;
/*
* At this point the only case left is that
* the two intervals are consecutive.
*/
return this.isConsecutiveTo( other );
}
/**
* Tells if the given interval can be subtracted from this one.
*
* When we work with sets, the operation of subtraction is always
* possible. If we subtract two sets, the result is still a set.
* When we work with intervals this is not true.
*
* By definition, a totally ordered set {@code I} is an interval
* if: for each {@code x} such that {@code inf(I) <= x <= sup(I)}
* we have that {@code x} belongs to {@code I}.
*
* If we subtract {@code (5,15)} from {@code [0,20]} the result is
* not an interval because, for example the value {@code 10}, does
* not belong to {@code [0,5]\u222a[15,20]}
*
* We can subtract an interval A from another interval B only of
* B is not entirely included into A. For example, from the interval
* {@code [0,20]}, we can subtract @code [10,20]} but cannot subtract
* {@code [10,15]} or {@code [10,20)}.
*
* A special case is given by the empty intervals. Se can always
* subtract an empty interval even if it is entirely included
* into the other one. For example, from the interval {@code [0,20]},
* we can subtract {@code [10,10)}, {@code (10,10]} and {@code (10,10)}.
*
* If this method returns {@code true} the method {@link #subtract(Interval)}
* will return a new {@link Interval} that represents this interval minus
* the given one. Otherwise, if this method returns {@code false}
* the method {@link #subtract(Interval)} will throw a {@link NoSuchIntervalException}.
*
* @param other the interval to check.
* @return {@code true} if this interval can subtract the other one.
* @throws RequirementFailure if the other interval is {@code null}.
*/
public boolean canSubtract( Interval other )
{
Require.nonNull( other, "The interval to check is mandatory" );
/* Empty intervals can always be subtracted. */
if( this.isEmpty() || other.isEmpty() )
return true;
/*
* A non empty interval A can subtract
* a non empty interval B if A do not
* entirely include B. This means that
* not both limits of B should lay
* inside the limits of A.
*/
return le(other.inf,this.inf) || ge(other.sup,this.sup);
}
/**
* Returns an interval containing only those
* elements that belongs to both this interval
* and the given one.
*
* If one of the intervals is the empty set or
* if the two intervals are disjoint then the
* empty set will be returned.
*
* @param other the interval to intersect.
* @return the intersection of the two intervals.
* @throws RequirementFailure if the other interval is {@code null}.
*/
public Interval intersect( Interval other )
{
Require.nonNull( other, "The interval to intersect is mandatory" );
/*
* If this includes other the intersection is other.
* This check includes also the case of empty intervals.
* For example if this = [0,10] and other = [5,5) then
* [5,5) will be returned.
*/
if( this.includes(other) )
return other;
/*
* If other includes this the intersection is this.
* This check includes also the case of empty intervals.
* For example if this = (5,5] and other = [0,10] then
* (5,5] will be returned.
*/
if( other.includes(this) )
return this;
/*
* At this point the two intervals can be disjoint
* or they are both non empty and overlap.
*/
if( this.isDisjointFrom(other) )
return empty();
/*
* Since the two intervals overlap, the intersection area
* is the one between the greatest inferior limit and the
* smallest superior limit.
*/
final IntervalLimit newInf = max( this.inf, other.inf );
final IntervalLimit newSup = min( this.sup, other.sup );
return new Interval<>( newInf, newSup );
}
/**
* Returns an interval containing those elements
* that belongs to at least one between this interval
* and the given one.
*
* If one of the intervals is the empty the other one
* will be returned.
*
* Two intervals can be unified if either they overlap
* or they are consecutive. If none of this condition
* holds then a {@link NoSuchIntervalException} will
* be thrown.
*
* If the methog {@link #canUnify(Interval)} returns {@code false}
* this method will throw a {@link NoSuchIntervalException}.
*
* @param other the interval to intersect.
* @return the intersection of the two intervals.
* @throws RequirementFailure if the other interval is {@code null}.
* @throws NoSuchIntervalException it the two intervals cannot be unified.
*/
public Interval unify( Interval other )
{
/*
* If this interval cannot be unified with the other
* then a NoSuchIntervalException will be thrown.
*/
if( ! canUnify(other) )
throw new NoSuchIntervalException( this, other, " \u222a " );
/* The empty set is the neutral element for union. */
if( this.isEmptySet() )
return other;
/* The empty set is the neutral element for union. */
if( other.isEmptySet() )
return this;
/*
* Otherwise this interval and the other either
* overlap or they are consecutive. In both cases
* the union will have the smallest inferior limit
* and the greatest superior limit.
*/
final IntervalLimit newInf = min( this.inf, other.inf );
final IntervalLimit newSup = max( this.sup, other.sup );
return new Interval<>( newInf, newSup );
}
/**
* Returns an interval containing only those
* elements that belongs to this interval
* but not to the given one.
*
* If one of the intervals is the empty set or
* if the two intervals are disjoint this interval
* will be returned.
*
* @param other the interval to subtract.
* @return the subtraction of the other interval from this one.
* @throws RequirementFailure if the other interval is {@code null}.
*/
public Interval subtract( Interval other )
{
Require.nonNull( other, "The interval to subtract is mandatory" );
/*
* If this interval cannot subtract the other
* then a NoSuchIntervalException will be thrown.
* This check makes sure that we cannot be in the
* situation where: this.includes(other) && ne(this,other).
*/
if( ! canSubtract(other) )
throw new NoSuchIntervalException( this, other, " \\ " );
/*
* If this interval is entirely included in the one to remove,
* for example [1,2] \ [0,3], then the empty set will be returned.
* This case include also the case in which the two intervals are equal.
*/
if( other.includes(this) )
return empty();
/*
* If the intervals are disjoint then subtracting
* the other interval will not change this.
* This case includes also the case where the
* interval to remove is empty.
*/
if( this.isDisjointFrom(other) )
return this;
/*
* At this point we have two intervals that are: not empty,
* not equal, not disjoint and are not including each other.
* So they must overlap, this means that we need to remove
* the overlapping portion from this interval.
* We need to check the unbounded cases separately.
*/
/*
* Case 1: the interval to remove has an unbounded inferior limit.
* Can be in the form (-inf,x) or (-inf,x]. In both cases we need
* to shift upwards the inferior limit of this interval.
* The interval to remove cannot be larger than this one otherwise
* we would have exit in the second check.
*/
if( other.inf.isUnbounded() )
{
final IntervalLimit newInf
= other.sup.isClosed()
? IntervalLimit.openInf( other.sup.value )
: IntervalLimit.closedInf( other.sup.value );
return new Interval( newInf, this.sup );
}
/*
* Case 2: the interval to remove has an unbounded superior limit.
* Can be in the form (x,+inf) or [x,+inf). In both cases we need
* to shift downwards the superior limit of this interval.
* The interval to remove cannot be larger than this one otherwise
* we would have exit in the second check.
*/
if( other.sup.isUnbounded() )
{
final IntervalLimit newSup
= other.inf.isClosed()
? IntervalLimit.openSup( other.inf.value )
: IntervalLimit.closedSup( other.inf.value );
return new Interval( this.inf, newSup );
}
/*
* Case 3: the interval to remove is bounded on both sides.
* Can be in the form (x,y) or [x,y), (x,y] or [x,x] with x < y.
* Since the other interval cannot be entirely included into
* this one it must be that one limit of other lays inside
* this interval and the other limit lays outside.
*/
if( lt(this.inf,other.inf) && ge(this.sup,other.inf) )
{
final IntervalLimit newSup
= other.inf.isClosed()
? IntervalLimit.openSup( other.inf.value )
: IntervalLimit.closedSup( other.inf.value );
return new Interval( this.inf, newSup );
}
else
{
final IntervalLimit newInf
= other.sup.isClosed()
? IntervalLimit.openInf( other.sup.value )
: IntervalLimit.closedInf( other.sup.value );
return new Interval( newInf, this.sup );
}
}
/* ****************** */
/* OBJECT OVERRIDES */
/* ****************** */
/**
* {@inheritDoc}
*/
@Override
public int hashCode()
{
return Hashcode.of( inf, sup );
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals( Object other )
{
return Equals.ifSameClass(
this, other,
o -> o.inf,
o -> o.sup
);
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
return isEmptySet() ? "\u2205" : inf + "," + sup;
}
}