jrdev / drange
Efficiently manage non-contiguous integer ranges with automatic merging and set operations
v1.1.0
2026-05-06 06:30 UTC
Requires
- php: >=8.2
Requires (Dev)
- captainhook/captainhook: ^5.29
- codeception/codeception: ^5.3
- codeception/module-asserts: ^3.2
- pdepend/pdepend: ^2.16
- phpmd/phpmd: ^2.15
- squizlabs/php_codesniffer: ^4.0
- dev-master
- v1.1.0
- v1.0.1
- v1.0.0
- dev-test-coverage-edge-cases-11954438648517271368
- dev-claude/check-ci-configs-ly0Hx
- dev-claude/implement-todo-item-jskpb
- dev-performance-optimization-remove-cloning-11430250651808090024
- dev-claude/add-github-test-workflow-GQ7lU
- dev-claude/enhance-readme-VtbuP
- dev-claude/setup-pre-push-hook-Afp5C
- dev-claude/update-packages-PwAvv
This package is auto-updated.
Last update: 2026-05-18 18:34:06 UTC
README
DRange is a PHP library for managing discontinuous (non-contiguous) ranges of integers. Rather than storing every number individually, it holds a compact, sorted list of contiguous sub-ranges — automatically merging adjacent ranges when you add numbers and splitting them when you subtract. The result is an expressive, memory-efficient structure for any problem that involves gaps in sequences.
Features
- Add, subtract, or intersect individual integers, numeric ranges,
SubRangeinstances, or entireDRangeobjects - Automatic merging of adjacent and overlapping ranges on
add() - Automatic splitting of ranges on
subtract() - Random-access by logical index (
->index($n)) across all sub-ranges - Native PHP
count()support viaCountable - Human-readable string representation (e.g.
[ 1-5, 8-10 ]) - Chainable
add()andsubtract()calls - Full clone support — deep copy, mutations on the clone do not affect the original
- Zero runtime dependencies
Requirements
- PHP >= 8.2
- Composer
Installation
composer require jrdev/drange
use jrdev\DRange; use jrdev\DRange\SubRange; // only needed if you use SubRange directly
Quick Start
$drange = new DRange(1, 5); // [ 1-5 ] $drange->add(8); // [ 1-5, 8 ] gap at 6-7 $drange->add(6, 7); // [ 1-8 ] auto-merged $drange->subtract(1, 3); // [ 4-8 ] echo $drange; // "[ 4-8 ]" echo count($drange); // 5
API Reference
DRange (jrdev\DRange)
| Method / Usage | Returns | Description |
|---|---|---|
new DRange() |
DRange |
Empty range |
new DRange($n) |
DRange |
Range containing the single integer $n |
new DRange($low, $high) |
DRange |
Contiguous range [$low..$high] |
->add($n) |
$this |
Add a single integer |
->add($low, $high) |
$this |
Add a contiguous range |
->add($drange) |
$this |
Add all sub-ranges from another DRange |
->add($subrange) |
$this |
Add a SubRange instance |
->subtract($n) |
$this |
Subtract a single integer |
->subtract($low, $high) |
$this |
Subtract a contiguous range |
->subtract($drange) |
$this |
Subtract all sub-ranges of another DRange |
->subtract($subrange) |
$this |
Subtract a SubRange instance |
->intersect($n) |
DRange |
New range containing only integers present in both this range and $n |
->intersect($low, $high) |
DRange |
Intersection with a contiguous range |
->intersect($drange) |
DRange |
Intersection with another DRange |
->intersect($subrange) |
DRange |
Intersection with a SubRange instance |
->index($i) |
int|null |
Value at logical position $i (0-based); null if out of bounds |
count($drange) |
int |
Total number of integers across all sub-ranges |
(string) $drange |
string |
e.g. "[ 1-5, 8-10 ]" |
clone $drange |
DRange |
Deep copy; mutations do not affect the original |
Adjacent ranges (touching but not overlapping) are merged automatically on every add() call.
SubRange (jrdev\DRange\SubRange)
| Member | Type | Description |
|---|---|---|
new SubRange($low, $high) |
— | Throws \UnexpectedValueException if $low > $high |
->low |
int |
Lower bound (inclusive) |
->high |
int |
Upper bound (inclusive) |
->length |
int |
$high - $low + 1 |
->overlaps($range) |
bool |
true if the two ranges share at least one integer |
->touches($range) |
bool |
true if the ranges overlap or are adjacent (no gap between them) |
->add($range) |
SubRange |
Merged range — only meaningful when touches() is true |
->subtract($range) |
SubRange[] |
Returns 0, 1, or 2 sub-ranges after removing the overlap |
(string) $subrange |
string |
"low-high" or just "n" for a single-number range |
clone $subrange |
SubRange |
Deep copy |
Usage Examples
Method chaining
$drange = new DRange(1, 10) ->subtract(5) ->subtract(7); // [ 1-4, 6, 8-10 ]
Operating on two DRange objects
$allowed = new DRange(1, 1023); $blocked = new DRange(22); $blocked->add(23)->add(3306)->add(5432); $available = clone $allowed; $available->subtract($blocked); // [ 1-21, 24-3305, 3307-5431, 5433-1023 ]
Random access with index()
$drange = new DRange(0, 9); // 10 elements: 0–9 $drange->add(20, 29); // 10 more: 20–29 $drange->add(40, 49); // 10 more: 40–49 $drange->index(0); // 0 (1st element) $drange->index(15); // 25 (16th element, 6th in second sub-range) $drange->index(25); // 45 (26th element, 6th in third sub-range) $drange->index(55); // null (out of bounds)
Counting with native count()
$drange = new DRange(1, 5); // 5 elements $drange->add(10, 15); // 6 more count($drange); // 11
Computing intersections with intersect()
$available = new DRange(1, 10); $available->add(20, 30); // [ 1-10, 20-30 ] $requested = new DRange(5, 25); $overlap = $available->intersect($requested); // [ 5-10, 20-25 ] count($overlap); // 12
Using SubRange directly
use jrdev\DRange\SubRange; $drange = new DRange(1, 10); $drange->subtract(new SubRange(4, 6)); // [ 1-3, 7-10 ]
Real-World Use Cases
- Network port management — Represent firewall allowlists or blocklists as unions of port ranges; subtract blocked ranges from the full
0–65535range to derive what is open. - HTTP range requests — Track which byte offsets of a large file have been downloaded; call
subtract()as each chunk arrives andcount()to know how many bytes remain. - Appointment and booking systems — Represent availability as numeric time ranges (minutes since midnight, Unix timestamps); subtract booked slots to see what is still open.
- Batch job progress tracking — Record which database record ID ranges have been processed; use
index()to resume from an arbitrary position without scanning the full set. - Pagination and lazy loading — Track which page or item index ranges have already been fetched from an API; avoid re-requesting pages that fall inside an already-loaded range.
- Seat allocation — Model auditorium or transit rows as numeric ranges; subtract reserved seats and inspect the remaining range to find the next available contiguous block.
- IP address management — Convert IPv4 addresses to 32-bit integers and use
DRangeto track allocated and free address blocks without needing a dedicated CIDR library.
License
Licensed under the MIT licence.