refactored to use collections instead of arrays for results

This commit is contained in:
Joby 2024-07-30 09:59:25 -06:00
parent 1ab8c481a2
commit e7d6aa07fa
5 changed files with 495 additions and 316 deletions

View file

@ -131,22 +131,28 @@ abstract class AbstractRange
* overlap this array will contain both ranges separately). Separate objects
* must be returned in ascending order.
* @param static $other
* @return static[]
* @return RangeCollection<static>
*/
public function booleanOr(AbstractRange $other): array
public function booleanOr(AbstractRange $other): RangeCollection
{
if ($this->intersects($other) || $this->adjacent($other)) {
return [
return RangeCollection::create(
new static(
$this->extendsBefore($other) ? $this->start() : $other->start(),
$this->extendsAfter($other) ? $this->end() : $other->end()
)
];
);
} else {
if ($this->extendsBefore($other)) {
return [new static($this->start(), $this->end()), new static($other->start(), $other->end())];
return RangeCollection::create(
new static($this->start(), $this->end()),
new static($other->start(), $other->end())
);
} else {
return [new static($other->start(), $other->end()), new static($this->start(), $this->end())];
return RangeCollection::create(
new static($other->start(), $other->end()),
new static($this->start(), $this->end())
);
}
}
}
@ -157,22 +163,17 @@ abstract class AbstractRange
* ranges do not overlap, this array will contain both ranges separately.
* Separate objects must be returned in ascending order.
* @param static $other
* @return static[]
* @return RangeCollection<static>
*/
public function booleanXor(AbstractRange $other): array
public function booleanXor(AbstractRange $other): RangeCollection
{
// if the ranges are equal, return an empty array
if ($this->equals($other)) return [];
if ($this->equals($other)) return RangeCollection::createEmpty($other);
// if the ranges are adjacent return a single range
if ($this->adjacentLeftOf($other)) return [new static($this->start(), $other->end())];
if ($this->adjacentRightOf($other)) return [new static($other->start(), $this->end())];
if ($this->adjacent($other)) return $this->booleanOr($other);
// if the ranges do not overlap, return both ranges
if (!$this->intersects($other)) {
if ($this->extendsBefore($other)) {
return [new static($this->start(), $this->end()), new static($other->start(), $other->end())];
} else {
return [new static($other->start(), $other->end()), new static($this->start(), $this->end())];
}
return RangeCollection::create(new static($this->start(), $this->end()), new static($other->start(), $other->end()));
}
// otherwise get the maximum bounds minus wherever these intersect
$range = new static(
@ -182,7 +183,7 @@ abstract class AbstractRange
if ($intersect = $this->booleanAnd($other)) {
return $range->booleanNot($intersect);
} else {
return [$range];
return RangeCollection::create($range);
}
}
@ -196,22 +197,18 @@ abstract class AbstractRange
* - 2 ranges: the entered ranges are adjacent, disjoint, or overlap with a shared boundary
* - 3 ranges: the entered ranges overlap with space on each end
* @param static $other
* @return static[]
* @return RangeCollection<static>
*/
public function booleanSlice(AbstractRange $other): array
public function booleanSlice(AbstractRange $other): RangeCollection
{
// if the ranges are equal, return a single range
if ($this->equals($other)) return [new static($this->start(), $this->end())];
// if the ranges are adjacent, return two ranges
if ($this->adjacentLeftOf($other)) return [new static($this->start(), $this->end()), new static($other->start(), $other->end())];
if ($this->adjacentRightOf($other)) return [new static($other->start(), $other->end()), new static($this->start(), $this->end())];
if ($this->equals($other)) return RangeCollection::create(new static($this->start(), $this->end()));
// if the ranges do not overlap, return two ranges
if (!$this->intersects($other)) {
if ($this->extendsBefore($other)) {
return [new static($this->start(), $this->end()), new static($other->start(), $other->end())];
} else {
return [new static($other->start(), $other->end()), new static($this->start(), $this->end())];
}
return RangeCollection::create(
new static($this->start(), $this->end()),
new static($other->start(), $other->end())
);
}
// otherwise get the maximum bounds minus wherever these intersect
$overall_range = new static(
@ -219,15 +216,14 @@ abstract class AbstractRange
$this->extendsAfter($other) ? $this->end() : $other->end()
);
$intersection = $this->booleanAnd($other);
assert($intersection !== null);
$xor = $overall_range->booleanNot($intersection);
if (count($xor) == 2) {
return [$xor[0], $intersection, $xor[1]];
assert(isset($xor[0], $xor[1]));
return RangeCollection::create($xor[0], $intersection, $xor[1]);
} elseif (count($xor) == 1) {
if ($intersection->extendsBefore($xor[0])) {
return [$intersection, $xor[0]];
} else {
return [$xor[0], $intersection];
}
assert(isset($xor[0]));
return RangeCollection::create($intersection, $xor[0]);
}
// throw an exception if we get in an unexpected state
throw new RuntimeException(sprintf("Unexpected state (%s,%s) (%s,%s)", $this->start, $this->end, $other->start, $other->end));
@ -239,38 +235,38 @@ abstract class AbstractRange
* the other range completely covers this range, an empty array will be
* returned. Separate objects must be returned in ascending order.
* @param static $other
* @return static[]
* @return RangeCollection<static>
*/
public function booleanNot(AbstractRange $other): array
public function booleanNot(AbstractRange $other): RangeCollection
{
// if this range is completely contained by the other, return an empty array
if ($other->contains($this)) {
return [];
return RangeCollection::createEmpty($other);
}
// if the ranges do not overlap, return this range
if (!$this->intersects($other)) {
return [new static($this->start(), $this->end())];
return RangeCollection::create(new static($this->start(), $this->end()));
}
// if this range completely contains the other, return the range from the start of this range to the start of the other
if ($this->contains($other)) {
if ($this->start == $other->start) {
return [new static(static::valueAfter($other->end), $this->end())];
return RangeCollection::create(new static(static::valueAfter($other->end), $this->end()));
} elseif ($this->end == $other->end) {
return [new static($this->start(), static::valueBefore($other->start))];
return RangeCollection::create(new static($this->start(), static::valueBefore($other->start)));
} else {
return [
return RangeCollection::create(
new static($this->start(), static::valueBefore($other->start)),
new static(static::valueAfter($other->end), $this->end())
];
);
}
}
// if this range extends before the other, return the range from the start of this range to the start of the other
if ($this->extendsBefore($other)) {
return [new static($this->start(), static::valueBefore($other->start))];
return RangeCollection::create(new static($this->start(), static::valueBefore($other->start)));
}
// if this range extends after the other, return the range from the end of the other to the end of this range
if ($this->extendsAfter($other)) {
return [new static(static::valueAfter($other->end), $this->end())];
return RangeCollection::create(new static(static::valueAfter($other->end), $this->end()));
}
// throw an exception if we get in an unexpected state
throw new RuntimeException(sprintf("Unexpected state (%s,%s) (%s,%s)", $this->start, $this->end, $other->start, $other->end));
@ -398,4 +394,14 @@ abstract class AbstractRange
{
return $this->end_value;
}
public function startAsNumber(): int|float
{
return $this->start;
}
public function endAsNumber(): int|float
{
return $this->end;
}
}

View file

@ -25,6 +25,8 @@
namespace Joby\Toolbox\Ranges;
use Stringable;
/**
* The simplest possible implementation of AbstractRange, which uses integers as
* its values as well as it's internal hashes. All it does to convert values is
@ -36,7 +38,7 @@ namespace Joby\Toolbox\Ranges;
*
* @extends AbstractRange<int>
*/
class IntegerRange extends AbstractRange
class IntegerRange extends AbstractRange implements Stringable
{
protected static function valueToInteger(mixed $value): int
{
@ -52,4 +54,13 @@ class IntegerRange extends AbstractRange
{
return (int)$value;
}
public function __toString(): string
{
return sprintf(
'[%s...%s]',
$this->start === -INF ? '' : $this->start,
$this->end === INF ? '' : $this->end
);
}
}

View file

@ -0,0 +1,166 @@
<?php
/**
* Joby's PHP Toolbox: https://code.byjoby.com/php-toolbox/
* MIT License: Copyright (c) 2024 Joby Elliott
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Joby\Toolbox\Ranges;
use ArrayAccess;
use ArrayIterator;
use Countable;
use InvalidArgumentException;
use IteratorAggregate;
use Joby\Toolbox\Sorting\Sorter;
/**
* @template T of AbstractRange
* @implements ArrayAccess<int,T>
* @implements IteratorAggregate<int,T>
*/
class RangeCollection implements Countable, ArrayAccess, IteratorAggregate
{
protected string $class;
/** @var T[] */
protected $ranges = [];
/**
* @template RangeType of AbstractRange
* @param RangeType $range
* @param RangeType ...$ranges
* @return RangeCollection<RangeType>
*/
public static function create(AbstractRange $range, AbstractRange ...$ranges): RangeCollection
{
return new RangeCollection($range::class, $range, ...$ranges);
}
/**
* @template RangeType of AbstractRange
* @param RangeType|class-string<RangeType> $class
* @return RangeCollection<RangeType>
*/
public static function createEmpty(AbstractRange|string $class): RangeCollection
{
if (is_object($class)) return new RangeCollection($class::class);
else return new RangeCollection($class);
}
/**
* @return T[]
*/
public function toArray(): array
{
return $this->ranges;
}
/**
* @param T ...$ranges
*/
public function add(AbstractRange ...$ranges): static
{
foreach ($ranges as $range) {
if (!($range instanceof $this->class)) {
throw new InvalidArgumentException("Ranges must be of type $this->class");
}
}
$this->ranges = array_merge($this->ranges, $ranges);
$this->sort();
return $this;
}
/**
* @param class-string<T> $class
* @param T ...$ranges
* @return void
*/
protected function __construct(string $class, AbstractRange ...$ranges)
{
$this->class = $class;
$this->add(...$ranges);
}
protected function sort(): void
{
static $sorter;
$sorter = $sorter ?? $sorter = new Sorter(
fn (AbstractRange $a, AbstractRange $b): int => $a->startAsNumber() <=> $b->startAsNumber(),
fn (AbstractRange $a, AbstractRange $b): int => $a->endAsNumber() <=> $b->endAsNumber(),
);
$sorter->sort($this->ranges);
}
public function count(): int
{
return count($this->ranges);
}
/**
* @param int $offset
* @return bool
*/
public function offsetExists($offset): bool
{
return isset($this->ranges[$offset]);
}
/**
* @param int $offset
* @return T|null
*/
public function offsetGet($offset): ?AbstractRange
{
return $this->ranges[$offset] ?? null;
}
/**
* @param int|null $offset
* @param T $value
*/
public function offsetSet($offset, $value): void
{
if (is_null($offset)) $this->add($value);
else {
if (!($value instanceof $this->class)) {
throw new InvalidArgumentException("Ranges must be of type $this->class");
}
$this->ranges[$offset] = $value;
$this->sort();
}
}
/**
* @param int $offset
*/
public function offsetUnset($offset): void
{
unset($this->ranges[$offset]);
}
/**
* @return ArrayIterator<int,T>
*/
public function getIterator(): ArrayIterator
{
return new ArrayIterator($this->ranges);
}
}

View file

@ -37,7 +37,7 @@ namespace Joby\Toolbox\Sorting;
*/
class Sorter
{
/** @var array<callable(mixed, mixed): int> */
/** @var array<callable> */
protected array $comparisons = [];
/**
@ -45,7 +45,7 @@ class Sorter
* The sorters will be called in order, and the array will be sorted based
* on the first one to return a non-zero value.
*
* @param callable(mixed, mixed): int ...$comparisons
* @param callable ...$comparisons
*/
public function __construct(callable ...$comparisons)
{
@ -56,7 +56,7 @@ class Sorter
* Add one or more sorting callbacks to this sorter. The new callbacks will
* be appended to the end of the existing list of sorters.
*
* @param callable(mixed, mixed): int ...$comparisons
* @param callable ...$comparisons
*/
public function addComparison(callable ...$comparisons): static
{

View file

@ -26,6 +26,7 @@
namespace Joby\Toolbox\Sorting;
use Joby\Toolbox\Ranges\IntegerRange;
use Joby\Toolbox\Ranges\RangeCollection;
use PHPUnit\Framework\TestCase;
class IntegerRangeTest extends TestCase
@ -660,14 +661,14 @@ class IntegerRangeTest extends TestCase
// fully bounded
$this->assertEquals(
[
'same' => '10,20',
'unbounded' => '10,20',
'intersecting open end left' => '10,20',
'intersecting open end right' => '11,20',
'intersecting open end center' => '10,20',
'intersecting open start left' => '10,19',
'intersecting open start right' => '10,20',
'intersecting open start center' => '10,20',
'same' => '[10...20]',
'unbounded' => '[10...20]',
'intersecting open end left' => '[10...20]',
'intersecting open end right' => '[11...20]',
'intersecting open end center' => '[10...20]',
'intersecting open start left' => '[10...19]',
'intersecting open start right' => '[10...20]',
'intersecting open start center' => '[10...20]',
'disjoint open start' => null,
'disjoint open end' => null,
'disjoint left' => null,
@ -676,16 +677,16 @@ class IntegerRangeTest extends TestCase
'adjacent open end' => null,
'adjacent left' => null,
'adjacent right' => null,
'contained' => '11,19',
'contained same start' => '10,19',
'contained same end' => '11,20',
'containing' => '10,20',
'containing unbounded start' => '10,20',
'containing unbounded end' => '10,20',
'containing same start' => '10,20',
'containing same end' => '10,20',
'containing same start unbounded end' => '10,20',
'containing same end unbounded start' => '10,20',
'contained' => '[11...19]',
'contained same start' => '[10...19]',
'contained same end' => '[11...20]',
'containing' => '[10...20]',
'containing unbounded start' => '[10...20]',
'containing unbounded end' => '[10...20]',
'containing same start' => '[10...20]',
'containing same end' => '[10...20]',
'containing same start unbounded end' => '[10...20]',
'containing same end unbounded start' => '[10...20]',
],
$this->scenarioResults(
new IntegerRange(10, 20),
@ -695,17 +696,17 @@ class IntegerRangeTest extends TestCase
// open start
$this->assertEquals(
[
'same' => 'null,20',
'unbounded' => 'null,20',
'intersecting open start left' => 'null,19',
'intersecting open start right' => 'null,20',
'intersecting bounded start left' => '9,19',
'intersecting bounded start right' => '11,20',
'intersecting bounded start center' => '10,20',
'intersecting open end same' => '20,20',
'intersecting open end left' => '19,20',
'intersecting bounded end same' => '20,20',
'intersecting bounded end left' => '19,20',
'same' => '[...20]',
'unbounded' => '[...20]',
'intersecting open start left' => '[...19]',
'intersecting open start right' => '[...20]',
'intersecting bounded start left' => '[9...19]',
'intersecting bounded start right' => '[11...20]',
'intersecting bounded start center' => '[10...20]',
'intersecting open end same' => '[20...20]',
'intersecting open end left' => '[19...20]',
'intersecting bounded end same' => '[20...20]',
'intersecting bounded end left' => '[19...20]',
'adjacent open end' => null,
'adjacent bounded end' => null,
'disjoint open end' => null,
@ -719,17 +720,17 @@ class IntegerRangeTest extends TestCase
// open end
$this->assertEquals(
[
'same' => '10,null',
'unbounded' => '10,null',
'intersecting open end left' => '10,null',
'intersecting open end right' => '11,null',
'intersecting bounded end left' => '10,19',
'intersecting bounded end right' => '11,21',
'intersecting bounded end center' => '10,20',
'intersecting open start same' => '10,10',
'intersecting open start right' => '10,11',
'intersecting bounded start same' => '10,10',
'intersecting bounded start right' => '10,11',
'same' => '[10...]',
'unbounded' => '[10...]',
'intersecting open end left' => '[10...]',
'intersecting open end right' => '[11...]',
'intersecting bounded end left' => '[10...19]',
'intersecting bounded end right' => '[11...21]',
'intersecting bounded end center' => '[10...20]',
'intersecting open start same' => '[10...10]',
'intersecting open start right' => '[10...11]',
'intersecting bounded start same' => '[10...10]',
'intersecting bounded start right' => '[10...11]',
'adjacent open start' => null,
'adjacent bounded start' => null,
'disjoint open start' => null,
@ -743,10 +744,10 @@ class IntegerRangeTest extends TestCase
// fully unbounded
$this->assertEquals(
[
'same' => 'null,null',
'open start' => 'null,20',
'open end' => '10,null',
'bounded' => '10,20',
'same' => '[...]',
'open start' => '[...20]',
'open end' => '[10...]',
'bounded' => '[10...20]',
],
$this->scenarioResults(
new IntegerRange(null, null),
@ -760,32 +761,32 @@ class IntegerRangeTest extends TestCase
// fully bounded
$this->assertEquals(
[
'same' => '10,20',
'unbounded' => 'null,null',
'intersecting open end left' => '9,null',
'intersecting open end right' => '10,null',
'intersecting open end center' => '10,null',
'intersecting open start left' => 'null,20',
'intersecting open start right' => 'null,21',
'intersecting open start center' => 'null,20',
'disjoint open start' => 'null,8;10,20',
'disjoint open end' => '10,20;22,null',
'disjoint left' => '-2,8;10,20',
'disjoint right' => '10,20;22,32',
'adjacent open start' => 'null,20',
'adjacent open end' => '10,null',
'adjacent left' => '-1,20',
'adjacent right' => '10,31',
'contained' => '10,20',
'contained same start' => '10,20',
'contained same end' => '10,20',
'containing' => '9,21',
'containing unbounded start' => 'null,21',
'containing unbounded end' => '9,null',
'containing same start' => '10,21',
'containing same end' => '9,20',
'containing same start unbounded end' => '10,null',
'containing same end unbounded start' => 'null,20',
'same' => '[10...20]',
'unbounded' => '[...]',
'intersecting open end left' => '[9...]',
'intersecting open end right' => '[10...]',
'intersecting open end center' => '[10...]',
'intersecting open start left' => '[...20]',
'intersecting open start right' => '[...21]',
'intersecting open start center' => '[...20]',
'disjoint open start' => '[...8] [10...20]',
'disjoint open end' => '[10...20] [22...]',
'disjoint left' => '[-2...8] [10...20]',
'disjoint right' => '[10...20] [22...32]',
'adjacent open start' => '[...20]',
'adjacent open end' => '[10...]',
'adjacent left' => '[-1...20]',
'adjacent right' => '[10...31]',
'contained' => '[10...20]',
'contained same start' => '[10...20]',
'contained same end' => '[10...20]',
'containing' => '[9...21]',
'containing unbounded start' => '[...21]',
'containing unbounded end' => '[9...]',
'containing same start' => '[10...21]',
'containing same end' => '[9...20]',
'containing same start unbounded end' => '[10...]',
'containing same end unbounded start' => '[...20]',
],
$this->scenarioResults(
new IntegerRange(10, 20),
@ -795,21 +796,21 @@ class IntegerRangeTest extends TestCase
// open start
$this->assertEquals(
[
'same' => 'null,20',
'unbounded' => 'null,null',
'intersecting open start left' => 'null,20',
'intersecting open start right' => 'null,21',
'intersecting bounded start left' => 'null,20',
'intersecting bounded start right' => 'null,21',
'intersecting bounded start center' => 'null,20',
'intersecting open end same' => 'null,null',
'intersecting open end left' => 'null,null',
'intersecting bounded end same' => 'null,30',
'intersecting bounded end left' => 'null,29',
'adjacent open end' => 'null,null',
'adjacent bounded end' => 'null,30',
'disjoint open end' => 'null,20;22,null',
'disjoint bounded end' => 'null,20;22,32',
'same' => '[...20]',
'unbounded' => '[...]',
'intersecting open start left' => '[...20]',
'intersecting open start right' => '[...21]',
'intersecting bounded start left' => '[...20]',
'intersecting bounded start right' => '[...21]',
'intersecting bounded start center' => '[...20]',
'intersecting open end same' => '[...]',
'intersecting open end left' => '[...]',
'intersecting bounded end same' => '[...30]',
'intersecting bounded end left' => '[...29]',
'adjacent open end' => '[...]',
'adjacent bounded end' => '[...30]',
'disjoint open end' => '[...20] [22...]',
'disjoint bounded end' => '[...20] [22...32]',
],
$this->scenarioResults(
new IntegerRange(null, 20),
@ -819,21 +820,21 @@ class IntegerRangeTest extends TestCase
// open end
$this->assertEquals(
[
'same' => '10,null',
'unbounded' => 'null,null',
'intersecting open end left' => '9,null',
'intersecting open end right' => '10,null',
'intersecting bounded end left' => '9,null',
'intersecting bounded end right' => '10,null',
'intersecting bounded end center' => '10,null',
'intersecting open start same' => 'null,null',
'intersecting open start right' => 'null,null',
'intersecting bounded start same' => '0,null',
'intersecting bounded start right' => '1,null',
'adjacent open start' => 'null,null',
'adjacent bounded start' => '0,null',
'disjoint open start' => 'null,8;10,null',
'disjoint bounded start' => '-2,8;10,null',
'same' => '[10...]',
'unbounded' => '[...]',
'intersecting open end left' => '[9...]',
'intersecting open end right' => '[10...]',
'intersecting bounded end left' => '[9...]',
'intersecting bounded end right' => '[10...]',
'intersecting bounded end center' => '[10...]',
'intersecting open start same' => '[...]',
'intersecting open start right' => '[...]',
'intersecting bounded start same' => '[0...]',
'intersecting bounded start right' => '[1...]',
'adjacent open start' => '[...]',
'adjacent bounded start' => '[0...]',
'disjoint open start' => '[...8] [10...]',
'disjoint bounded start' => '[-2...8] [10...]',
],
$this->scenarioResults(
new IntegerRange(10, null),
@ -843,10 +844,10 @@ class IntegerRangeTest extends TestCase
// fully unbounded
$this->assertEquals(
[
'same' => 'null,null',
'open start' => 'null,null',
'open end' => 'null,null',
'bounded' => 'null,null',
'same' => '[...]',
'open start' => '[...]',
'open end' => '[...]',
'bounded' => '[...]',
],
$this->scenarioResults(
new IntegerRange(null, null),
@ -863,22 +864,22 @@ class IntegerRangeTest extends TestCase
'same' => '',
'unbounded' => '',
'intersecting open end left' => '',
'intersecting open end right' => '10,10',
'intersecting open end right' => '[10...10]',
'intersecting open end center' => '',
'intersecting open start left' => '20,20',
'intersecting open start left' => '[20...20]',
'intersecting open start right' => '',
'intersecting open start center' => '',
'disjoint open start' => '10,20',
'disjoint open end' => '10,20',
'disjoint left' => '10,20',
'disjoint right' => '10,20',
'adjacent open start' => '10,20',
'adjacent open end' => '10,20',
'adjacent left' => '10,20',
'adjacent right' => '10,20',
'contained' => '10,10;20,20',
'contained same start' => '20,20',
'contained same end' => '10,10',
'disjoint open start' => '[10...20]',
'disjoint open end' => '[10...20]',
'disjoint left' => '[10...20]',
'disjoint right' => '[10...20]',
'adjacent open start' => '[10...20]',
'adjacent open end' => '[10...20]',
'adjacent left' => '[10...20]',
'adjacent right' => '[10...20]',
'contained' => '[10...10] [20...20]',
'contained same start' => '[20...20]',
'contained same end' => '[10...10]',
'containing' => '',
'containing unbounded start' => '',
'containing unbounded end' => '',
@ -897,19 +898,19 @@ class IntegerRangeTest extends TestCase
[
'same' => '',
'unbounded' => '',
'intersecting open start left' => '20,20',
'intersecting open start left' => '[20...20]',
'intersecting open start right' => '',
'intersecting bounded start left' => 'null,8;20,20',
'intersecting bounded start right' => 'null,10',
'intersecting bounded start center' => 'null,9',
'intersecting open end same' => 'null,19',
'intersecting open end left' => 'null,18',
'intersecting bounded end same' => 'null,19',
'intersecting bounded end left' => 'null,18',
'adjacent open end' => 'null,20',
'adjacent bounded end' => 'null,20',
'disjoint open end' => 'null,20',
'disjoint bounded end' => 'null,20',
'intersecting bounded start left' => '[...8] [20...20]',
'intersecting bounded start right' => '[...10]',
'intersecting bounded start center' => '[...9]',
'intersecting open end same' => '[...19]',
'intersecting open end left' => '[...18]',
'intersecting bounded end same' => '[...19]',
'intersecting bounded end left' => '[...18]',
'adjacent open end' => '[...20]',
'adjacent bounded end' => '[...20]',
'disjoint open end' => '[...20]',
'disjoint bounded end' => '[...20]',
],
$this->scenarioResults(
new IntegerRange(null, 20),
@ -922,18 +923,18 @@ class IntegerRangeTest extends TestCase
'same' => '',
'unbounded' => '',
'intersecting open end left' => '',
'intersecting open end right' => '10,10',
'intersecting bounded end left' => '20,null',
'intersecting bounded end right' => '10,10;22,null',
'intersecting bounded end center' => '21,null',
'intersecting open start same' => '11,null',
'intersecting open start right' => '12,null',
'intersecting bounded start same' => '11,null',
'intersecting bounded start right' => '12,null',
'adjacent open start' => '10,null',
'adjacent bounded start' => '10,null',
'disjoint open start' => '10,null',
'disjoint bounded start' => '10,null',
'intersecting open end right' => '[10...10]',
'intersecting bounded end left' => '[20...]',
'intersecting bounded end right' => '[10...10] [22...]',
'intersecting bounded end center' => '[21...]',
'intersecting open start same' => '[11...]',
'intersecting open start right' => '[12...]',
'intersecting bounded start same' => '[11...]',
'intersecting bounded start right' => '[12...]',
'adjacent open start' => '[10...]',
'adjacent bounded start' => '[10...]',
'disjoint open start' => '[10...]',
'disjoint bounded start' => '[10...]',
],
$this->scenarioResults(
new IntegerRange(10, null),
@ -944,9 +945,9 @@ class IntegerRangeTest extends TestCase
$this->assertEquals(
[
'same' => '',
'open start' => '21,null',
'open end' => 'null,9',
'bounded' => 'null,9;21,null',
'open start' => '[21...]',
'open end' => '[...9]',
'bounded' => '[...9] [21...]',
],
$this->scenarioResults(
new IntegerRange(null, null),
@ -961,31 +962,31 @@ class IntegerRangeTest extends TestCase
$this->assertEquals(
[
'same' => '',
'unbounded' => 'null,9;21,null',
'intersecting open end left' => '9,9;21,null',
'intersecting open end right' => '10,10;21,null',
'intersecting open end center' => '21,null',
'intersecting open start left' => 'null,9;20,20',
'intersecting open start right' => 'null,9;21,21',
'intersecting open start center' => 'null,9',
'disjoint open start' => 'null,8;10,20',
'disjoint open end' => '10,20;22,null',
'disjoint left' => '-2,8;10,20',
'disjoint right' => '10,20;22,32',
'adjacent open start' => 'null,20',
'adjacent open end' => '10,null',
'adjacent left' => '-1,20',
'adjacent right' => '10,31',
'contained' => '10,10;20,20',
'contained same start' => '20,20',
'contained same end' => '10,10',
'containing' => '9,9;21,21',
'containing unbounded start' => 'null,9;21,21',
'containing unbounded end' => '9,9;21,null',
'containing same start' => '21,21',
'containing same end' => '9,9',
'containing same start unbounded end' => '21,null',
'containing same end unbounded start' => 'null,9',
'unbounded' => '[...9] [21...]',
'intersecting open end left' => '[9...9] [21...]',
'intersecting open end right' => '[10...10] [21...]',
'intersecting open end center' => '[21...]',
'intersecting open start left' => '[...9] [20...20]',
'intersecting open start right' => '[...9] [21...21]',
'intersecting open start center' => '[...9]',
'disjoint open start' => '[...8] [10...20]',
'disjoint open end' => '[10...20] [22...]',
'disjoint left' => '[-2...8] [10...20]',
'disjoint right' => '[10...20] [22...32]',
'adjacent open start' => '[...20]',
'adjacent open end' => '[10...]',
'adjacent left' => '[-1...20]',
'adjacent right' => '[10...31]',
'contained' => '[10...10] [20...20]',
'contained same start' => '[20...20]',
'contained same end' => '[10...10]',
'containing' => '[9...9] [21...21]',
'containing unbounded start' => '[...9] [21...21]',
'containing unbounded end' => '[9...9] [21...]',
'containing same start' => '[21...21]',
'containing same end' => '[9...9]',
'containing same start unbounded end' => '[21...]',
'containing same end unbounded start' => '[...9]',
],
$this->scenarioResults(
new IntegerRange(10, 20),
@ -996,20 +997,20 @@ class IntegerRangeTest extends TestCase
$this->assertEquals(
[
'same' => '',
'unbounded' => '21,null',
'intersecting open start left' => '20,20',
'intersecting open start right' => '21,21',
'intersecting bounded start left' => 'null,8;20,20',
'intersecting bounded start right' => 'null,10;21,21',
'intersecting bounded start center' => 'null,9',
'intersecting open end same' => 'null,19;21,null',
'intersecting open end left' => 'null,18;21,null',
'intersecting bounded end same' => 'null,19;21,30',
'intersecting bounded end left' => 'null,18;21,29',
'adjacent open end' => 'null,null',
'adjacent bounded end' => 'null,30',
'disjoint open end' => 'null,20;22,null',
'disjoint bounded end' => 'null,20;22,32',
'unbounded' => '[21...]',
'intersecting open start left' => '[20...20]',
'intersecting open start right' => '[21...21]',
'intersecting bounded start left' => '[...8] [20...20]',
'intersecting bounded start right' => '[...10] [21...21]',
'intersecting bounded start center' => '[...9]',
'intersecting open end same' => '[...19] [21...]',
'intersecting open end left' => '[...18] [21...]',
'intersecting bounded end same' => '[...19] [21...30]',
'intersecting bounded end left' => '[...18] [21...29]',
'adjacent open end' => '[...]',
'adjacent bounded end' => '[...30]',
'disjoint open end' => '[...20] [22...]',
'disjoint bounded end' => '[...20] [22...32]',
],
$this->scenarioResults(
new IntegerRange(null, 20),
@ -1020,20 +1021,20 @@ class IntegerRangeTest extends TestCase
$this->assertEquals(
[
'same' => '',
'unbounded' => 'null,9',
'intersecting open end left' => '9,9',
'intersecting open end right' => '10,10',
'intersecting bounded end left' => '9,9;20,null',
'intersecting bounded end right' => '10,10;22,null',
'intersecting bounded end center' => '21,null',
'intersecting open start same' => 'null,9;11,null',
'intersecting open start right' => 'null,9;12,null',
'intersecting bounded start same' => '0,9;11,null',
'intersecting bounded start right' => '1,9;12,null',
'adjacent open start' => 'null,null',
'adjacent bounded start' => '0,null',
'disjoint open start' => 'null,8;10,null',
'disjoint bounded start' => '-2,8;10,null',
'unbounded' => '[...9]',
'intersecting open end left' => '[9...9]',
'intersecting open end right' => '[10...10]',
'intersecting bounded end left' => '[9...9] [20...]',
'intersecting bounded end right' => '[10...10] [22...]',
'intersecting bounded end center' => '[21...]',
'intersecting open start same' => '[...9] [11...]',
'intersecting open start right' => '[...9] [12...]',
'intersecting bounded start same' => '[0...9] [11...]',
'intersecting bounded start right' => '[1...9] [12...]',
'adjacent open start' => '[...]',
'adjacent bounded start' => '[0...]',
'disjoint open start' => '[...8] [10...]',
'disjoint bounded start' => '[-2...8] [10...]',
],
$this->scenarioResults(
new IntegerRange(10, null),
@ -1044,9 +1045,9 @@ class IntegerRangeTest extends TestCase
$this->assertEquals(
[
'same' => '',
'open start' => '21,null',
'open end' => 'null,9',
'bounded' => 'null,9;21,null',
'open start' => '[21...]',
'open end' => '[...9]',
'bounded' => '[...9] [21...]',
],
$this->scenarioResults(
new IntegerRange(null, null),
@ -1060,32 +1061,32 @@ class IntegerRangeTest extends TestCase
// fully bounded
$this->assertEquals(
[
'same' => '10,20',
'unbounded' => 'null,9;10,20;21,null',
'intersecting open end left' => '9,9;10,20;21,null',
'intersecting open end right' => '10,10;11,20;21,null',
'intersecting open end center' => '10,20;21,null',
'intersecting open start left' => 'null,9;10,19;20,20',
'intersecting open start right' => 'null,9;10,20;21,21',
'intersecting open start center' => 'null,9;10,20',
'disjoint open start' => 'null,8;10,20',
'disjoint open end' => '10,20;22,null',
'disjoint left' => '-2,8;10,20',
'disjoint right' => '10,20;22,32',
'adjacent open start' => 'null,9;10,20',
'adjacent open end' => '10,20;21,null',
'adjacent left' => '-1,9;10,20',
'adjacent right' => '10,20;21,31',
'contained' => '10,10;11,19;20,20',
'contained same start' => '10,19;20,20',
'contained same end' => '10,10;11,20',
'containing' => '9,9;10,20;21,21',
'containing unbounded start' => 'null,9;10,20;21,21',
'containing unbounded end' => '9,9;10,20;21,null',
'containing same start' => '10,20;21,21',
'containing same end' => '9,9;10,20',
'containing same start unbounded end' => '10,20;21,null',
'containing same end unbounded start' => 'null,9;10,20',
'same' => '[10...20]',
'unbounded' => '[...9] [10...20] [21...]',
'intersecting open end left' => '[9...9] [10...20] [21...]',
'intersecting open end right' => '[10...10] [11...20] [21...]',
'intersecting open end center' => '[10...20] [21...]',
'intersecting open start left' => '[...9] [10...19] [20...20]',
'intersecting open start right' => '[...9] [10...20] [21...21]',
'intersecting open start center' => '[...9] [10...20]',
'disjoint open start' => '[...8] [10...20]',
'disjoint open end' => '[10...20] [22...]',
'disjoint left' => '[-2...8] [10...20]',
'disjoint right' => '[10...20] [22...32]',
'adjacent open start' => '[...9] [10...20]',
'adjacent open end' => '[10...20] [21...]',
'adjacent left' => '[-1...9] [10...20]',
'adjacent right' => '[10...20] [21...31]',
'contained' => '[10...10] [11...19] [20...20]',
'contained same start' => '[10...19] [20...20]',
'contained same end' => '[10...10] [11...20]',
'containing' => '[9...9] [10...20] [21...21]',
'containing unbounded start' => '[...9] [10...20] [21...21]',
'containing unbounded end' => '[9...9] [10...20] [21...]',
'containing same start' => '[10...20] [21...21]',
'containing same end' => '[9...9] [10...20]',
'containing same start unbounded end' => '[10...20] [21...]',
'containing same end unbounded start' => '[...9] [10...20]',
],
$this->scenarioResults(
new IntegerRange(10, 20),
@ -1095,21 +1096,21 @@ class IntegerRangeTest extends TestCase
// open start
$this->assertEquals(
[
'same' => 'null,20',
'unbounded' => 'null,20;21,null',
'intersecting open start left' => 'null,19;20,20',
'intersecting open start right' => 'null,20;21,21',
'intersecting bounded start left' => 'null,8;9,19;20,20',
'intersecting bounded start right' => 'null,10;11,20;21,21',
'intersecting bounded start center' => 'null,9;10,20',
'intersecting open end same' => 'null,19;20,20;21,null',
'intersecting open end left' => 'null,18;19,20;21,null',
'intersecting bounded end same' => 'null,19;20,20;21,30',
'intersecting bounded end left' => 'null,18;19,20;21,29',
'adjacent open end' => 'null,20;21,null',
'adjacent bounded end' => 'null,20;21,30',
'disjoint open end' => 'null,20;22,null',
'disjoint bounded end' => 'null,20;22,32',
'same' => '[...20]',
'unbounded' => '[...20] [21...]',
'intersecting open start left' => '[...19] [20...20]',
'intersecting open start right' => '[...20] [21...21]',
'intersecting bounded start left' => '[...8] [9...19] [20...20]',
'intersecting bounded start right' => '[...10] [11...20] [21...21]',
'intersecting bounded start center' => '[...9] [10...20]',
'intersecting open end same' => '[...19] [20...20] [21...]',
'intersecting open end left' => '[...18] [19...20] [21...]',
'intersecting bounded end same' => '[...19] [20...20] [21...30]',
'intersecting bounded end left' => '[...18] [19...20] [21...29]',
'adjacent open end' => '[...20] [21...]',
'adjacent bounded end' => '[...20] [21...30]',
'disjoint open end' => '[...20] [22...]',
'disjoint bounded end' => '[...20] [22...32]',
],
$this->scenarioResults(
new IntegerRange(null, 20),
@ -1119,21 +1120,21 @@ class IntegerRangeTest extends TestCase
// open end
$this->assertEquals(
[
'same' => '10,null',
'unbounded' => 'null,9;10,null',
'intersecting open end left' => '9,9;10,null',
'intersecting open end right' => '10,10;11,null',
'intersecting bounded end left' => '9,9;10,19;20,null',
'intersecting bounded end right' => '10,10;11,21;22,null',
'intersecting bounded end center' => '10,20;21,null',
'intersecting open start same' => 'null,9;10,10;11,null',
'intersecting open start right' => 'null,9;10,11;12,null',
'intersecting bounded start same' => '0,9;10,10;11,null',
'intersecting bounded start right' => '1,9;10,11;12,null',
'adjacent open start' => 'null,9;10,null',
'adjacent bounded start' => '0,9;10,null',
'disjoint open start' => 'null,8;10,null',
'disjoint bounded start' => '-2,8;10,null',
'same' => '[10...]',
'unbounded' => '[...9] [10...]',
'intersecting open end left' => '[9...9] [10...]',
'intersecting open end right' => '[10...10] [11...]',
'intersecting bounded end left' => '[9...9] [10...19] [20...]',
'intersecting bounded end right' => '[10...10] [11...21] [22...]',
'intersecting bounded end center' => '[10...20] [21...]',
'intersecting open start same' => '[...9] [10...10] [11...]',
'intersecting open start right' => '[...9] [10...11] [12...]',
'intersecting bounded start same' => '[0...9] [10...10] [11...]',
'intersecting bounded start right' => '[1...9] [10...11] [12...]',
'adjacent open start' => '[...9] [10...]',
'adjacent bounded start' => '[0...9] [10...]',
'disjoint open start' => '[...8] [10...]',
'disjoint bounded start' => '[-2...8] [10...]',
],
$this->scenarioResults(
new IntegerRange(10, null),
@ -1143,10 +1144,10 @@ class IntegerRangeTest extends TestCase
// fully unbounded
$this->assertEquals(
[
'same' => 'null,null',
'open start' => 'null,20;21,null',
'open end' => 'null,9;10,null',
'bounded' => 'null,9;10,20;21,null',
'same' => '[...]',
'open start' => '[...20] [21...]',
'open end' => '[...9] [10...]',
'bounded' => '[...9] [10...20] [21...]',
],
$this->scenarioResults(
new IntegerRange(null, null),
@ -1242,15 +1243,10 @@ class IntegerRangeTest extends TestCase
function ($s) use ($range, $method) {
$result = $range->$method($s);
if ($result instanceof IntegerRange) {
$result = ($result->start() ?? 'null') . ',' . ($result->end() ?? 'null');
$result = (string)$result;
}
if (is_array($result)) {
$result = implode(';', array_map(
function ($r) {
return ($r->start() ?? 'null') . ',' . ($r->end() ?? 'null');
},
$result
));
if ($result instanceof RangeCollection) {
$result = implode(' ', $result->toArray());
}
return $result;
},