Interval.java
package org.djutils.math.functions;
import java.util.Objects;
import org.djutils.exceptions.Throw;
/**
* Immutable double interval, optionally including none, one, or both boundary values.
* <p>
* Copyright (c) 2024-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
* for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
* distributed under a three-clause BSD-style license, which can be found at
* <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>.
* </p>
* @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
* @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
* @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
* @param <T> the payload type
* @param low low limit of the domain
* @param lowInclusive if true; the low limit is included; if false, the low limit is not included
* @param high high limit of the domain (inclusive)
* @param highInclusive if true; the high limit is included; if false; the high limit is not included
* @param payload the payload of this Interval. This is can be anything, but it is usually the MathFunction that is active in
* this Interval. When the Interval field is not used, supply any comparable class (e.g. String) and provide a null
* value to the constructor of Interval
*/
record Interval<T extends Comparable<T>>(double low, boolean lowInclusive, double high, boolean highInclusive, T payload)
implements Comparable<Interval<T>>
{
/**
* Construct a new Interval.
* @param low low limit of the domain
* @param lowInclusive if true; the low limit is included; if false, the low limit is not included
* @param high high limit of the domain (inclusive)
* @param highInclusive if true; the high limit is included; if false; the high limit is not included
* @param payload the payload of this Interval
*/
Interval(final double low, final boolean lowInclusive, final double high, final boolean highInclusive, final T payload)
{
Throw.when(low > high, IllegalArgumentException.class, "low may not be higher than high");
Throw.when(low == high && (!lowInclusive) && (!highInclusive), IllegalArgumentException.class,
"zero width interval must include at least one of its boundaries");
this.low = low;
this.lowInclusive = lowInclusive;
this.high = high;
this.highInclusive = highInclusive;
this.payload = payload;
}
/**
* Check if this Interval completely covers some other Interval.
* @param other the other Interval (not necessarily carrying a similarly typed pay load)
* @return boolean; true if this Interval completely covers the other Interval; false if any part of other Interval (which
* may be infinitesimally small) is outside this Interval
*/
public boolean covers(final Interval<?> other)
{
if (this.low > other.low || this.high < other.high)
{
return false;
}
if (this.low == other.low && (!this.lowInclusive) && other.lowInclusive)
{
return false;
}
if (this.high == other.high && (!this.highInclusive) && other.highInclusive)
{
return false;
}
return true;
}
/**
* Check if this Interval is completely disjunct of some other Interval.
* @param other the other Interval (not necessarily carrying a similarly typed pay load)
* @return boolean; true if this Interval is completely disjunct of the other Interval; false if any part of this Interval
* (which may be infinitesimally small) covers the other Interval
*/
public boolean disjunct(final Interval<?> other)
{
if (this.high < other.low || this.low > other.high)
{
return true;
}
if (this.high == other.low && ((!this.highInclusive) || (!other.lowInclusive)))
{
return true;
}
if (this.low == other.high && ((!this.lowInclusive) || (!other.highInclusive)))
{
return true;
}
return false;
}
@Override
public int compareTo(final Interval<T> other)
{
// compare the low boundary
if (this.low < other.low || this.low == other.low && this.lowInclusive && (!other.lowInclusive))
{
return -1;
}
if (this.low > other.low || this.low == other.low && other.lowInclusive && (!this.lowInclusive))
{
return 1;
}
// low and lowInclusive are the same; compare the high boundary
if (this.high < other.high || this.high == other.high && (!this.highInclusive) && other.highInclusive)
{
return -1;
}
if (this.high > other.high || this.high == other.high && this.highInclusive && (!other.highInclusive))
{
return 1;
}
// boundaries are exactly the same; compare the payload
if (this.payload != null)
{
if (other.payload == null)
{
return -1;
}
return this.payload.compareTo(other.payload);
}
if (other.payload != null)
{
return 1;
}
return 0;
}
/**
* Check if a value falls on this Interval.
* @param x the value to check
* @return true if <code>x</code> lies on this Interval
*/
public boolean covers(final double x)
{
return ((this.low < x || (this.low == x && this.lowInclusive))
&& (this.high > x || (this.high == x && this.highInclusive)));
}
/**
* Compute the intersection of this <code>Interval</code> and some other <code>Interval</code>. The other Interval need not
* have the same type of payload.
* @param other the other <code>Interval</code>
* @return the intersection of the intervals (can be <code>null</code>). If not null, the payload is the payload of
* <code>this</code> interval
*/
public Interval<T> intersection(final Interval<?> other)
{
if (this.disjunct(other))
{
return null;
}
if (other.covers(this))
{
return this;
}
if (this.covers(other))
{
return new Interval<T>(other.low, other.lowInclusive, other.high, other.highInclusive, this.payload);
}
boolean includeLow = this.low > other.low && this.lowInclusive || this.low < other.low && other.lowInclusive
|| this.low == other.low && this.lowInclusive && other.lowInclusive;
boolean includeHigh = this.high < other.high && this.highInclusive || this.high > other.high && other.highInclusive
|| this.high == other.high && this.highInclusive && other.highInclusive;
return new Interval<T>(Math.max(this.low, other.low), includeLow, Math.min(this.high, other.high), includeHigh,
this.payload);
}
@Override
public String toString()
{
return (this.lowInclusive ? "[" : "(") + this.low + ", " + this.high + (this.highInclusive ? "]" : ")") + "\u2192"
+ this.payload;
}
@Override
public int hashCode()
{
return Objects.hash(this.high, this.highInclusive, this.low, this.lowInclusive, this.payload);
}
@SuppressWarnings("checkstyle:needbraces")
@Override
public boolean equals(final Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Interval<?> other = (Interval<?>) obj;
return Double.doubleToLongBits(this.high) == Double.doubleToLongBits(other.high)
&& this.highInclusive == other.highInclusive
&& Double.doubleToLongBits(this.low) == Double.doubleToLongBits(other.low)
&& this.lowInclusive == other.lowInclusive && Objects.equals(this.payload, other.payload);
}
}