Skip to content

Commit 8bedbb3

Browse files
blaze6950Mykyta Zotov
andauthored
Refactor range domain extension methods for improved clarity and performance, introducing internal operations for span, shift, and expand functionalities. (#4)
Co-authored-by: Mykyta Zotov <mykyta.zotov@ihsmarkit.com>
1 parent 4e656eb commit 8bedbb3

11 files changed

Lines changed: 826 additions & 467 deletions

File tree

src/Domain/Intervals.NET.Domain.Extensions/CommonRangeDomainExtensions.cs

Lines changed: 0 additions & 171 deletions
This file was deleted.

src/Domain/Intervals.NET.Domain.Extensions/Fixed/RangeDomainExtensions.cs

Lines changed: 114 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -81,74 +81,7 @@ public static class RangeDomainExtensions
8181
/// </remarks>
8282
public static RangeValue<long> Span<TRangeValue, TDomain>(this Range<TRangeValue> range, TDomain domain)
8383
where TRangeValue : IComparable<TRangeValue>
84-
where TDomain : IFixedStepDomain<TRangeValue>
85-
{
86-
// If either boundary is unbounded in the direction that expands the range, span is infinite
87-
if (range.Start.IsNegativeInfinity || range.End.IsPositiveInfinity)
88-
{
89-
return RangeValue<long>.PositiveInfinity;
90-
}
91-
92-
var firstStep = CalculateFirstStep(range, domain);
93-
var lastStep = CalculateLastStep(range, domain);
94-
95-
// After domain alignment, boundaries can cross (e.g., open range smaller than one step)
96-
// Example: (Jan 1 00:00, Jan 1 00:01) with day domain -> firstStep=Jan 2, lastStep=Dec 31
97-
if (firstStep.CompareTo(lastStep) > 0)
98-
{
99-
return 0;
100-
}
101-
102-
if (firstStep.CompareTo(lastStep) == 0)
103-
{
104-
return HandleSingleStepCase(range, domain);
105-
}
106-
107-
var distance = domain.Distance(firstStep, lastStep);
108-
return distance + 1;
109-
110-
// Local functions
111-
static TRangeValue CalculateFirstStep(Range<TRangeValue> r, TDomain d)
112-
{
113-
if (r.IsStartInclusive)
114-
{
115-
// Include boundary: use floor to include the step we're on/in
116-
return d.Floor(r.Start.Value);
117-
}
118-
119-
// Exclude boundary: floor to get the boundary, then add 1 to skip it
120-
var flooredStart = d.Floor(r.Start.Value);
121-
return d.Add(flooredStart, 1);
122-
}
123-
124-
static TRangeValue CalculateLastStep(Range<TRangeValue> r, TDomain d)
125-
{
126-
if (r.IsEndInclusive)
127-
{
128-
// Include boundary: use floor to include the step we're on/in
129-
return d.Floor(r.End.Value);
130-
}
131-
132-
// Exclude boundary: floor to get the boundary, then subtract 1 to exclude it
133-
var flooredEnd = d.Floor(r.End.Value);
134-
return d.Add(flooredEnd, -1);
135-
}
136-
137-
static long HandleSingleStepCase(Range<TRangeValue> r, TDomain d)
138-
{
139-
// If both floor to the same step, check if either bound is actually ON that step
140-
var startIsOnBoundary = d.Floor(r.Start.Value).CompareTo(r.Start.Value) == 0;
141-
var endIsOnBoundary = d.Floor(r.End.Value).CompareTo(r.End.Value) == 0;
142-
143-
if (r is { IsStartInclusive: true, IsEndInclusive: true } && (startIsOnBoundary || endIsOnBoundary))
144-
{
145-
return 1;
146-
}
147-
148-
// Otherwise, they're in between domain steps, return 0
149-
return 0;
150-
}
151-
}
84+
where TDomain : IFixedStepDomain<TRangeValue> => Internal.RangeDomainOperations.CalculateSpan(range, domain);
15285

15386
/// <summary>
15487
/// Expands the given range by specified ratios on the left and right sides using the provided fixed-step domain.
@@ -185,6 +118,12 @@ static long HandleSingleStepCase(Range<TRangeValue> r, TDomain d)
185118
/// The offset is calculated as <c>(long)(span * ratio)</c>, which truncates any fractional part.
186119
/// For fixed-step domains, span is always a long integer, so no precision loss occurs.
187120
/// </para>
121+
/// <para>
122+
/// Note: the supplied "ratio" values are coefficients, not percentages. You can convert a coefficient to a
123+
/// percentage by multiplying by 100 (for example: 1 -> 100%, 0.5 -> 50%, 0 -> 0%). Conceptually, think of the
124+
/// coefficients as discrete counts of domain steps proportional to the current span. Negative coefficients behave
125+
/// like negative offsets in the default <c>Expand</c> method (they move the boundary inward rather than outward).
126+
/// </para>
188127
/// <para><strong>Example:</strong></para>
189128
/// <code>
190129
/// var range = Range.Closed(10, 20); // span = 11
@@ -199,6 +138,11 @@ static long HandleSingleStepCase(Range<TRangeValue> r, TDomain d)
199138
/// var expanded2 = range.ExpandByRatio(domain, 0.4, 0.4);
200139
/// // Calculation: leftOffset = (long)(11 * 0.4) = (long)4.4 = 4
201140
/// // Result: [6, 24] (truncates to 4 steps)
141+
///
142+
/// // Negative ratios contract the range (behave like negative offsets):
143+
/// var contracted = range.ExpandByRatio(domain, -0.2, -0.2);
144+
/// // Calculation: leftOffset = (long)(11 * -0.2) = (long)-2.2 = -2
145+
/// // Result: [12, 18] (contracted inward by 2 steps on each side)
202146
/// </code>
203147
/// </remarks>
204148
public static Range<TRangeValue> ExpandByRatio<TRangeValue, TDomain>(
@@ -208,18 +152,108 @@ public static Range<TRangeValue> ExpandByRatio<TRangeValue, TDomain>(
208152
double rightRatio
209153
)
210154
where TRangeValue : IComparable<TRangeValue>
211-
where TDomain : IFixedStepDomain<TRangeValue>
212-
{
213-
var distance = range.Span(domain);
155+
where TDomain : IFixedStepDomain<TRangeValue> =>
156+
Internal.RangeDomainOperations.ExpandByRatio(range, domain, leftRatio, rightRatio);
214157

215-
if (!distance.IsFinite)
216-
{
217-
throw new ArgumentException("Cannot expand range by ratio when span is infinite.", nameof(range));
218-
}
219-
220-
var leftOffset = (long)(distance.Value * leftRatio);
221-
var rightOffset = (long)(distance.Value * rightRatio);
158+
/// <summary>
159+
/// Shifts the given range by the specified offset using the provided fixed-step domain.
160+
/// <para>
161+
/// ⚡ <strong>Performance:</strong> O(1) - Constant time.
162+
/// </para>
163+
/// </summary>
164+
/// <param name="range">The range to be shifted.</param>
165+
/// <param name="domain">The fixed-step domain that defines how to add an offset to values of type T.</param>
166+
/// <param name="offset">
167+
/// The offset by which to shift the range. Positive values shift the range forward,
168+
/// negative values shift it backward.
169+
/// </param>
170+
/// <typeparam name="TRangeValue">The type of the values in the range. Must implement IComparable&lt;T&gt;.</typeparam>
171+
/// <typeparam name="TDomain">The type of the domain that implements IFixedStepDomain&lt;TRangeValue&gt;.</typeparam>
172+
/// <returns>A new <see cref="Range{T}"/> instance representing the shifted range with the same inclusivity.</returns>
173+
/// <remarks>
174+
/// <para>
175+
/// This operation preserves:
176+
/// </para>
177+
/// <list type="bullet">
178+
/// <item><description>Range inclusivity flags (both start and end)</description></item>
179+
/// <item><description>Infinite boundaries (infinity + offset = infinity)</description></item>
180+
/// <item><description>Relative distance between boundaries</description></item>
181+
/// </list>
182+
///
183+
/// <para><strong>Examples:</strong></para>
184+
/// <code>
185+
/// var range = Range.Closed(10, 20); // [10, 20]
186+
/// var domain = new IntegerFixedStepDomain();
187+
///
188+
/// var shifted = range.Shift(domain, 5); // [15, 25] - O(1)
189+
/// var shiftedBack = range.Shift(domain, -3); // [7, 17] - O(1)
190+
/// </code>
191+
///
192+
/// <para><strong>Performance:</strong></para>
193+
/// <para>
194+
/// O(1) - Fixed-step domains use arithmetic for Add(), ensuring constant-time performance.
195+
/// </para>
196+
/// </remarks>
197+
public static Range<TRangeValue> Shift<TRangeValue, TDomain>(
198+
this Range<TRangeValue> range,
199+
TDomain domain,
200+
long offset
201+
)
202+
where TRangeValue : IComparable<TRangeValue>
203+
where TDomain : IFixedStepDomain<TRangeValue> => Internal.RangeDomainOperations.Shift(range, domain, offset);
222204

223-
return range.Expand(domain, leftOffset, rightOffset);
224-
}
205+
/// <summary>
206+
/// Expands the given range by the specified amounts on the left and right sides using the provided fixed-step domain.
207+
/// <para>
208+
/// ⚡ <strong>Performance:</strong> O(1) - Constant time.
209+
/// </para>
210+
/// </summary>
211+
/// <param name="range">The range to be expanded.</param>
212+
/// <param name="domain">The fixed-step domain that defines how to add an offset to values of type T.</param>
213+
/// <param name="left">
214+
/// The amount to expand the range on the left side. Positive values expand the range to the left
215+
/// (move start boundary backward), while negative values contract it (move start forward).
216+
/// </param>
217+
/// <param name="right">
218+
/// The amount to expand the range on the right side. Positive values expand the range to the right
219+
/// (move end boundary forward), while negative values contract it (move end backward).
220+
/// </param>
221+
/// <typeparam name="TRangeValue">The type of the values in the range. Must implement IComparable&lt;T&gt;.</typeparam>
222+
/// <typeparam name="TDomain">The type of the domain that implements IFixedStepDomain&lt;TRangeValue&gt;.</typeparam>
223+
/// <returns>A new <see cref="Range{T}"/> instance representing the expanded range.</returns>
224+
/// <remarks>
225+
/// <para>
226+
/// This operation allows asymmetric expansion - you can expand different amounts on each side.
227+
/// Negative values cause contraction instead of expansion.
228+
/// </para>
229+
///
230+
/// <para><strong>Examples:</strong></para>
231+
/// <code>
232+
/// var range = Range.Closed(10, 20); // [10, 20]
233+
/// var domain = new IntegerFixedStepDomain();
234+
///
235+
/// var expanded = range.Expand(domain, left: 2, right: 3); // [8, 23] - O(1)
236+
/// var contracted = range.Expand(domain, left: -2, right: -3); // [12, 17] - O(1)
237+
/// var asymmetric = range.Expand(domain, left: 5, right: 0); // [5, 20] - O(1)
238+
/// </code>
239+
///
240+
/// <para><strong>Performance:</strong></para>
241+
/// <para>
242+
/// O(1) - Fixed-step domains use arithmetic for Add(), ensuring constant-time performance.
243+
/// </para>
244+
///
245+
/// <para><strong>See Also:</strong></para>
246+
/// <list type="bullet">
247+
/// <item><description><c>ExpandByRatio</c> - For proportional expansion based on range span</description></item>
248+
/// </list>
249+
/// </remarks>
250+
public static Range<TRangeValue> Expand<TRangeValue, TDomain>(
251+
this Range<TRangeValue> range,
252+
TDomain domain,
253+
long left = 0,
254+
long right = 0
255+
)
256+
where TRangeValue : IComparable<TRangeValue>
257+
where TDomain : IFixedStepDomain<TRangeValue> =>
258+
Internal.RangeDomainOperations.Expand(range, domain, left, right);
225259
}

0 commit comments

Comments
 (0)