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. This is can be anything, but it is usually the MathFunction that is active in
24   *            this Interval. When the Interval field is not used, supply any comparable class (e.g. String) and provide a null
25   *            value to the constructor of Interval
26   */
27  record Interval<T extends Comparable<T>>(double low, boolean lowInclusive, double high, boolean highInclusive, T payload)
28          implements Comparable<Interval<T>>
29  {
30      /**
31       * Construct a new Interval.
32       * @param low low limit of the domain
33       * @param lowInclusive if true; the low limit is included; if false, the low limit is not included
34       * @param high high limit of the domain (inclusive)
35       * @param highInclusive if true; the high limit is included; if false; the high limit is not included
36       * @param payload the payload of this Interval
37       */
38      Interval(final double low, final boolean lowInclusive, final double high, final boolean highInclusive, final T payload)
39      {
40          Throw.when(low > high, IllegalArgumentException.class, "low may not be higher than high");
41          Throw.when(low == high && (!lowInclusive) && (!highInclusive), IllegalArgumentException.class,
42                  "zero width interval must include at least one of its boundaries");
43          this.low = low;
44          this.lowInclusive = lowInclusive;
45          this.high = high;
46          this.highInclusive = highInclusive;
47          this.payload = payload;
48      }
49  
50      /**
51       * Check if this Interval completely covers some other Interval.
52       * @param other the other Interval (not necessarily carrying a similarly typed pay load)
53       * @return boolean; true if this Interval completely covers the other Interval; false if any part of other Interval (which
54       *         may be infinitesimally small) is outside this Interval
55       */
56      public boolean covers(final Interval<?> other)
57      {
58          if (this.low > other.low || this.high < other.high)
59          {
60              return false;
61          }
62          if (this.low == other.low && (!this.lowInclusive) && other.lowInclusive)
63          {
64              return false;
65          }
66          if (this.high == other.high && (!this.highInclusive) && other.highInclusive)
67          {
68              return false;
69          }
70          return true;
71      }
72  
73      /**
74       * Check if this Interval is completely disjunct of some other Interval.
75       * @param other the other Interval (not necessarily carrying a similarly typed pay load)
76       * @return boolean; true if this Interval is completely disjunct of the other Interval; false if any part of this Interval
77       *         (which may be infinitesimally small) covers the other Interval
78       */
79      public boolean disjunct(final Interval<?> other)
80      {
81          if (this.high < other.low || this.low > other.high)
82          {
83              return true;
84          }
85          if (this.high == other.low && ((!this.highInclusive) || (!other.lowInclusive)))
86          {
87              return true;
88          }
89          if (this.low == other.high && ((!this.lowInclusive) || (!other.highInclusive)))
90          {
91              return true;
92          }
93          return false;
94      }
95  
96      @Override
97      public int compareTo(final Interval<T> other)
98      {
99          // compare the low boundary
100         if (this.low < other.low || this.low == other.low && this.lowInclusive && (!other.lowInclusive))
101         {
102             return -1;
103         }
104         if (this.low > other.low || this.low == other.low && other.lowInclusive && (!this.lowInclusive))
105         {
106             return 1;
107         }
108         // low and lowInclusive are the same; compare the high boundary
109         if (this.high < other.high || this.high == other.high && (!this.highInclusive) && other.highInclusive)
110         {
111             return -1;
112         }
113         if (this.high > other.high || this.high == other.high && this.highInclusive && (!other.highInclusive))
114         {
115             return 1;
116         }
117         // boundaries are exactly the same; compare the payload
118         if (this.payload != null)
119         {
120             if (other.payload == null)
121             {
122                 return -1;
123             }
124             return this.payload.compareTo(other.payload);
125         }
126         if (other.payload != null)
127         {
128             return 1;
129         }
130         return 0;
131     }
132 
133     /**
134      * Check if a value falls on this Interval.
135      * @param x the value to check
136      * @return true if <code>x</code> lies on this Interval
137      */
138     public boolean covers(final double x)
139     {
140         return ((this.low < x || (this.low == x && this.lowInclusive))
141                 && (this.high > x || (this.high == x && this.highInclusive)));
142     }
143 
144     /**
145      * Compute the intersection of this <code>Interval</code> and some other <code>Interval</code>. The other Interval need not
146      * have the same type of payload.
147      * @param other the other <code>Interval</code>
148      * @return the intersection of the intervals (can be <code>null</code>). If not null, the payload is the payload of
149      *         <code>this</code> interval
150      */
151     public Interval<T> intersection(final Interval<?> other)
152     {
153         if (this.disjunct(other))
154         {
155             return null;
156         }
157         if (other.covers(this))
158         {
159             return this;
160         }
161         if (this.covers(other))
162         {
163             return new Interval<T>(other.low, other.lowInclusive, other.high, other.highInclusive, this.payload);
164         }
165         boolean includeLow = this.low > other.low && this.lowInclusive || this.low < other.low && other.lowInclusive
166                 || this.low == other.low && this.lowInclusive && other.lowInclusive;
167         boolean includeHigh = this.high < other.high && this.highInclusive || this.high > other.high && other.highInclusive
168                 || this.high == other.high && this.highInclusive && other.highInclusive;
169         return new Interval<T>(Math.max(this.low, other.low), includeLow, Math.min(this.high, other.high), includeHigh,
170                 this.payload);
171     }
172 
173     @Override
174     public String toString()
175     {
176         return (this.lowInclusive ? "[" : "(") + this.low + ", " + this.high + (this.highInclusive ? "]" : ")") + "\u2192"
177                 + this.payload;
178     }
179 
180     @Override
181     public int hashCode()
182     {
183         return Objects.hash(this.high, this.highInclusive, this.low, this.lowInclusive, this.payload);
184     }
185 
186     @SuppressWarnings("checkstyle:needbraces")
187     @Override
188     public boolean equals(final Object obj)
189     {
190         if (this == obj)
191             return true;
192         if (obj == null)
193             return false;
194         if (getClass() != obj.getClass())
195             return false;
196         Interval<?> other = (Interval<?>) obj;
197         return Double.doubleToLongBits(this.high) == Double.doubleToLongBits(other.high)
198                 && this.highInclusive == other.highInclusive
199                 && Double.doubleToLongBits(this.low) == Double.doubleToLongBits(other.low)
200                 && this.lowInclusive == other.lowInclusive && Objects.equals(this.payload, other.payload);
201     }
202 
203 }