View Javadoc
1   package org.djutils.math.functions;
2   
3   import java.util.Objects;
4   
5   import org.djutils.exceptions.Throw;
6   
7   /**
8    * Immutable double interval, optionally including none, one, or both boundary values.
9    * <p>
10   * Copyright (c) 2024-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
11   * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
12   * distributed under a three-clause BSD-style license, which can be found at
13   * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>.
14   * </p>
15   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
16   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
17   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
18   * @param <T> the payload type
19   * @param low low limit of the domain
20   * @param lowInclusive if true; the low limit is included; if false, the low limit is not included
21   * @param high high limit of the domain (inclusive)
22   * @param highInclusive if true; the high limit is included; if false; the high limit is not included
23   * @param payload the payload of this Interval
24   */
25  record Interval<T extends Comparable<T>>(double low, boolean lowInclusive, double high, boolean highInclusive, T payload)
26          implements Comparable<Interval<?>>
27  {
28      /**
29       * Construct a new Interval.
30       * @param low low limit of the domain
31       * @param lowInclusive if true; the low limit is included; if false, the low limit is not included
32       * @param high high limit of the domain (inclusive)
33       * @param highInclusive if true; the high limit is included; if false; the high limit is not included
34       * @param payload the payload of this Interval
35       */
36      Interval(final double low, final boolean lowInclusive, final double high, final boolean highInclusive, final T payload)
37      {
38          Throw.when(low > high, IllegalArgumentException.class, "low may not be higher than high");
39          Throw.when(low == high && (!lowInclusive) && (!highInclusive), IllegalArgumentException.class,
40                  "zero width interval must include at least one of its boundaries");
41          this.low = low;
42          this.lowInclusive = lowInclusive;
43          this.high = high;
44          this.highInclusive = highInclusive;
45          this.payload = payload;
46      }
47  
48      /**
49       * Check if this Interval completely covers some other Interval.
50       * @param other the other Interval (not necessarily carrying a similarly typed pay load)
51       * @return boolean; true if this Interval completely covers the other Interval; false if any part of other Interval (which
52       *         may be infinitesimally small) is outside this Interval
53       */
54      public boolean covers(final Interval<?> other)
55      {
56          if (this.low > other.low || this.high < other.high)
57          {
58              return false;
59          }
60          if (this.low == other.low && (!this.lowInclusive) && other.lowInclusive)
61          {
62              return false;
63          }
64          if (this.high == other.high && (!this.highInclusive) && other.highInclusive)
65          {
66              return false;
67          }
68          return true;
69      }
70  
71      /**
72       * Check if this Interval is completely disjunct of some other Interval.
73       * @param other the other Interval (not necessarily carrying a similarly typed pay load)
74       * @return boolean; true if this Interval is completely disjunct of the other Interval; false if any part of this Interval
75       *         (which may be infinitesimally small) covers the other Interval
76       */
77      public boolean disjunct(final Interval<?> other)
78      {
79          if (this.high < other.low || this.low > other.high)
80          {
81              return true;
82          }
83          if (this.high == other.low && ((!this.highInclusive) || (!other.lowInclusive)))
84          {
85              return true;
86          }
87          if (this.low == other.high && ((!this.lowInclusive) || (!other.highInclusive)))
88          {
89              return true;
90          }
91          return false;
92      }
93  
94      @SuppressWarnings("unchecked")
95      @Override
96      public int compareTo(final Interval<?> other)
97      {
98          // compare the low boundary
99          if (this.low < other.low || this.low == other.low && this.lowInclusive && (!other.lowInclusive))
100         {
101             return -1;
102         }
103         if (this.low > other.low || this.low == other.low && other.lowInclusive && (!this.lowInclusive))
104         {
105             return 1;
106         }
107         // low and lowInclusive are the same; compare the high boundary
108         if (this.high < other.high || this.high == other.high && (!this.highInclusive) && other.highInclusive)
109         {
110             return -1;
111         }
112         if (this.high > other.high || this.high == other.high && this.highInclusive && (!other.highInclusive))
113         {
114             return 1;
115         }
116         // boundaries are exactly the same; compare the payload
117         if (this.payload != null)
118         {
119             if (other.payload == null)
120             {
121                 return -1;
122             }
123             return this.payload.compareTo((T) other.payload);
124         }
125         if (other.payload != null)
126         {
127             return 1;
128         }
129         return 0;
130     }
131 
132     /**
133      * Check if a value falls on this Interval.
134      * @param x the value to check
135      * @return true if <code>x</code> lies on this Interval
136      */
137     public boolean covers(final double x)
138     {
139         return ((this.low < x || (this.low == x && this.lowInclusive))
140                 && (this.high > x || (this.high == x && this.highInclusive)));
141     }
142 
143     /**
144      * Compute the intersection of this <code>Interval</code> and some other <code>Interval</code>. The other Interval need not
145      * have the same type of payload.
146      * @param other the other <code>Interval</code>
147      * @return the intersection of the intervals (can be <code>null</code>). If not null, the payload is the payload of
148      *         <code>this</code> interval
149      */
150     public Interval<T> intersection(final Interval<?> other)
151     {
152         if (this.disjunct(other))
153         {
154             return null;
155         }
156         if (other.covers(this))
157         {
158             return this;
159         }
160         if (this.covers(other))
161         {
162             return new Interval<T>(other.low, other.lowInclusive, other.high, other.highInclusive, this.payload);
163         }
164         boolean includeLow = this.low > other.low && this.lowInclusive || this.low < other.low && other.lowInclusive
165                 || this.low == other.low && this.lowInclusive && other.lowInclusive;
166         boolean includeHigh = this.high < other.high && this.highInclusive || this.high > other.high && other.highInclusive
167                 || this.high == other.high && this.highInclusive && other.highInclusive;
168         return new Interval<T>(Math.max(this.low, other.low), includeLow, Math.min(this.high, other.high), includeHigh,
169                 this.payload);
170     }
171 
172     @Override
173     public String toString()
174     {
175         return (this.lowInclusive ? "[" : "(") + this.low + ", " + this.high + (this.highInclusive ? "]" : ")") + "\u2192"
176                 + this.payload;
177     }
178 
179     @Override
180     public int hashCode()
181     {
182         return Objects.hash(this.high, this.highInclusive, this.low, this.lowInclusive, this.payload);
183     }
184 
185     @SuppressWarnings("checkstyle:needbraces")
186     @Override
187     public boolean equals(final Object obj)
188     {
189         if (this == obj)
190             return true;
191         if (obj == null)
192             return false;
193         if (getClass() != obj.getClass())
194             return false;
195         Interval<?> other = (Interval<?>) obj;
196         return Double.doubleToLongBits(this.high) == Double.doubleToLongBits(other.high)
197                 && this.highInclusive == other.highInclusive
198                 && Double.doubleToLongBits(this.low) == Double.doubleToLongBits(other.low)
199                 && this.lowInclusive == other.lowInclusive && Objects.equals(this.payload, other.payload);
200     }
201 
202 }