/************************************************************************* * ModernUO * * Copyright 2019-2026 - ModernUO Development Team * * Email: hi@modernuo.com * * File: SortedRangeIndex.cs * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * *************************************************************************/ using System; using System.Numerics; namespace Server.Collections; /// /// Immutable, allocation-lean membership index over a set of inclusive integer ranges. Ranges are /// stored as two parallel arrays (_mins/_maxs) sorted by minimum and coalesced into /// disjoint runs, so a single binary search decides membership. Coalescing is required for /// correctness: only inspects the rightmost run whose minimum is <= the /// value, which is only sound when the runs never overlap or nest. /// public sealed class SortedRangeIndex where T : IBinaryInteger { public static readonly SortedRangeIndex Empty = new([], []); /// An inclusive [Min, Max] range. Singles are represented as Min == Max. public readonly record struct Range(T Min, T Max); /// Orders ranges ascending by minimum; the coalescing pass in requires this. public static readonly Comparison ByMin = static (a, b) => a.Min.CompareTo(b.Min); private readonly T[] _mins; private readonly T[] _maxs; private SortedRangeIndex(T[] mins, T[] maxs) { _mins = mins; _maxs = maxs; } public int Count => _mins.Length; /// /// True when falls in any range. Binary-searches for the rightmost run /// whose minimum is <= the value, then tests that value against that run's maximum. /// public bool Contains(T value) { var lo = 0; var hi = _mins.Length - 1; var found = -1; while (lo <= hi) { var mid = (lo + hi) >> 1; if (_mins[mid] <= value) { found = mid; lo = mid + 1; } else { hi = mid - 1; } } return found >= 0 && value <= _maxs[found]; } /// /// Builds an index from ranges that are already sorted ascending by (sort /// the source with first). Overlapping and nested ranges are merged into disjoint /// runs. Two passes over the (pooled) input keep the run count exact so only the two final arrays are /// heap-allocated: pass one counts the runs, pass two fills the exact-size arrays. /// public static SortedRangeIndex Build(ReadOnlySpan sortedByMin) { if (sortedByMin.IsEmpty) { return Empty; } // Pass 1: count the disjoint runs so the final arrays can be sized exactly. var runs = 1; var curMax = sortedByMin[0].Max; for (var i = 1; i < sortedByMin.Length; i++) { var r = sortedByMin[i]; if (r.Min <= curMax) { if (r.Max > curMax) { curMax = r.Max; } } else { runs++; curMax = r.Max; } } // Pass 2: write the coalesced runs into the exact-size final arrays. var mins = new T[runs]; var maxs = new T[runs]; var w = 0; mins[0] = sortedByMin[0].Min; maxs[0] = sortedByMin[0].Max; for (var i = 1; i < sortedByMin.Length; i++) { var r = sortedByMin[i]; if (r.Min <= maxs[w]) { if (r.Max > maxs[w]) { maxs[w] = r.Max; } } else { w++; mins[w] = r.Min; maxs[w] = r.Max; } } return new SortedRangeIndex(mins, maxs); } }