From 7b4363e2238bfdd488ec7a720903daca70a48bb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=90=E6=97=AD=20=E8=8B=8F?= Date: Fri, 25 Sep 2020 18:04:15 +0800 Subject: [PATCH] PHP7.4 new feature. Add method getAllValue() --- .gitignore | 2 + README.md | 22 +- src/AbstractSegmentTree.php | 253 +++++----- src/MixtureFlag.php | 35 +- src/Node.php | 395 ++++++++-------- src/SegmentTreeValueNotFoundException.php | 7 +- src/Tree/Common.php | 256 ++++++----- src/Tree/Date.php | 390 ++++++++-------- src/Tree/Version.php | 533 +++++++++++----------- 9 files changed, 994 insertions(+), 899 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b1bde73 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/composer.lock +/cli/ diff --git a/README.md b/README.md index d519740..e5446c3 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,13 @@ $tree->setValue(50000, 60000, 'key2', new \SplQueue()); $tree->setValue(99999, 100000, 'key3', 'some value'); // Get value of certain position. -var_dump($tree->getValue(20006)); +var_dump($tree->getValue(20006, 'key1')); -// Delete value of between certain postions. +// Delete value of between certain positions. $tree->delValue(30000, 100000, 'key1'); -// Thorws exception when value not found. -var_dump($tree->getValue(70000)); +// Throws exception when value not found. +var_dump($tree->getValue(70000, 'key1')); // Get segment arrays. var_dump($tree->getSegmentsOfGivenKey('key1')); @@ -46,10 +46,18 @@ $tree = Swango\SegmentTree\Tree\Version::newTree( '1.0.0', '1.0.1', '1.0.2', '1.0.3', '1.1.0', '1.4.1', '2.0.0', '2.0.1-RC1', '3.0.0' ); // Create a tree with versions. These versions will be sorted using version_compare() and remove all duplicated -// All methos are similar with Common tree. -$tree->setValue('1.0.2', '2.0.0', 'key1', $true); +// All methods are similar with Common tree. +$tree->setValue('1.0.2', '2.0.0', 'key1', true); var_dump($tree->getValue('1.1.0')); ``` ### Date compressed segment tree -(todo) +```php +$tree = Swango\SegmentTree\Tree\Date::newTree( + '2020-09-25', '2020-12-12' +); // Create a tree with dates. + +// All methods are similar with Common tree. +$tree->setValue('2020-09-29', '2020-11-11', 'key1', true); +var_dump($tree->getValue('2020-11-01')); +``` diff --git a/src/AbstractSegmentTree.php b/src/AbstractSegmentTree.php index 2e1ecc9..8126013 100644 --- a/src/AbstractSegmentTree.php +++ b/src/AbstractSegmentTree.php @@ -1,126 +1,129 @@ -data as $key=>&$tmp) - $ret[] = $key; - return $key; - } - protected $use_double_equal_sign = true; - protected function isEqual($x, $y): bool { - if ($x instanceof MixtureFlag || $y instanceof MixtureFlag) - return false; - if ($this->use_double_equal_sign) - return $x == $y; - else - return $x === $y; - } - /** - * Set to use "==" when comparing values. - * Two objects of different instances that have same content will be considered equal. - * - * This is a default setting. No need to call manualy. - * Must be called before set any value or things could be corrupted. - * - * @return self - */ - public function useDoubleEqualSign(): self { - $this->use_double_equal_sign = true; - return $this; - } - /** - * Set to use "===" when comparing values. - * Two objects of different instances will be considered not equal no matter their content. - * - * Must be called before set any value or things could be corrupted. - * - * @return self - */ - public function useTripleEqualSign(): self { - $this->use_double_equal_sign = false; - return $this; - } - /** - * fill $key of the whole tree by $value - * - * @param string $key - * @param mixed $value - * @return self - */ - public function fill(string $key, $value): self { - $this->_setValue($this->l, $this->r, $key, $value); - return $this; - } - /** - * Clear all values and child nodes. - * Make it a new tree. - * - * @return self - */ - public function clear(): self { - $this->data = new \stdClass(); - $this->node_l = null; - $this->node_r = null; - return $this; - } - /** - * Remove all redundant nodes in the tree to reduce memory cost. - * - * @return self - */ - public function optimize(): self { - if (! $this->hasChildNode()) - return $this; - $queue = new \SplQueue(); - $queue->enqueue($this); - do { - /** - * - * @var Node $node - */ - $node = $queue->dequeue(); - foreach ($node->data as $value) - if ($value instanceof MixtureFlag) { - if ($node->node_l->hasChildNode()) - $queue->enqueue($node->node_l); - if ($node->node_r->hasChildNode()) - $queue->enqueue($node->node_r); - continue 2; - } - - $node->node_l = null; - $node->node_r = null; - } while ( ! $queue->isEmpty() ); - return $this; - } - /** - * Get number of nodes in the tree - * - * @return int - */ - public function getNodesCount(): int { - $count = 0; - $queue = new \SplQueue(); - $queue->enqueue($this); - do { - ++ $count; - /** - * - * @var Node $node - */ - $node = $queue->dequeue(); - if ($node->hasChildNode()) { - $queue->enqueue($node->node_l); - $queue->enqueue($node->node_r); - } - } while ( ! $queue->isEmpty() ); - return $count; - } +data as $key => &$tmp) + $ret[] = $key; + return $key; + } + protected bool $use_double_equal_sign = true; + protected function isEqual($x, $y): bool { + if ($x instanceof MixtureFlag || $y instanceof MixtureFlag) { + return false; + } + if ($this->use_double_equal_sign) { + return $x == $y; + } else { + return $x === $y; + } + } + /** + * Set to use "==" when comparing values. + * Two objects of different instances that have same content will be considered equal. + * + * This is a default setting. No need to call manually. + * Must be called before set any value or things could be corrupted. + * + * @return self + */ + public function useDoubleEqualSign(): self { + $this->use_double_equal_sign = true; + return $this; + } + /** + * Set to use "===" when comparing values. + * Two objects of different instances will be considered not equal no matter their content. + * + * Must be called before set any value or things could be corrupted. + * + * @return self + */ + public function useTripleEqualSign(): self { + $this->use_double_equal_sign = false; + return $this; + } + /** + * fill $key of the whole tree by $value + * + * @param string $key + * @param mixed $value + * @return self + */ + public function fill(string $key, $value): self { + $this->_setValue($this->l, $this->r, $key, $value); + return $this; + } + /** + * Clear all values and child nodes. + * Make it a new tree. + * + * @return self + */ + public function clear(): self { + $this->data = new \stdClass(); + unset($this->node_l); + unset($this->node_r); + return $this; + } + /** + * Remove all redundant nodes in the tree to reduce memory cost. + * + * @return self + */ + public function optimize(): self { + if (! $this->hasChildNode()) { + return $this; + } + $queue = new \SplQueue(); + $queue->enqueue($this); + do { + /** + * + * @var Node $node + */ + $node = $queue->dequeue(); + foreach ($node->data as $value) + if ($value instanceof MixtureFlag) { + if ($node->node_l->hasChildNode()) { + $queue->enqueue($node->node_l); + } + if ($node->node_r->hasChildNode()) { + $queue->enqueue($node->node_r); + } + continue 2; + } + unset($this->node_l); + unset($this->node_r); + } while (! $queue->isEmpty()); + return $this; + } + /** + * Get number of nodes in the tree + * + * @return int + */ + public function getNodesCount(): int { + $count = 0; + $queue = new \SplQueue(); + $queue->enqueue($this); + do { + ++$count; + /** + * @var Node $node + */ + $node = $queue->dequeue(); + if ($node->hasChildNode()) { + $queue->enqueue($node->node_l); + $queue->enqueue($node->node_r); + } + } while (! $queue->isEmpty()); + return $count; + } } \ No newline at end of file diff --git a/src/MixtureFlag.php b/src/MixtureFlag.php index 3d13bc3..a4a7d27 100644 --- a/src/MixtureFlag.php +++ b/src/MixtureFlag.php @@ -1,19 +1,18 @@ -r - $this->l + 1; - } - abstract protected function isEqual($x, $y): bool; - /** - * - * @var int $l - * @var int $r - * @var Node $node_l - * @var Node $node_r - * @var object $data - */ - protected $l, $r, $node_l, $node_r, $data; - protected function __construct(int $l, int $r) { - $this->l = $l; - $this->r = $r; - $this->data = new \stdClass(); - } - protected function hasChildNode(): bool { - return isset($this->node_l); - } - protected function isLeafNode(): bool { - return $this->l === $this->r; - } - protected function getMiddle(): int { - return intdiv($this->l + $this->r, 2); - } - protected function _getValue(int $position, string $key) { - if (! property_exists($this->data, $key)) - throw new SegmentTreeValueNotFoundException(); - - $value = $this->data->{$key}; - if ($value instanceof MixtureFlag) { - if ($position <= $this->getMiddle()) - return $this->node_l->_getValue($position, $key); - else - return $this->node_r->_getValue($position, $key); - } else { - return $value; - } - } - protected function _exists(int $l, int $r, string $key): bool { - if (! property_exists($this->data, $key)) - return false; - if (! $this->data->{$key} instanceof MixtureFlag) - return true; - - $middle = $this->getMiddle(); - if ($l <= $middle && $this->node_l->_exists($l, $r, $key)) - return true; - - if ($r > $middle && $this->node_r->_exists($l, $r, $key)) - return true; - - return false; - } - protected function _getSegmentsOfGivenKeyAndValue(string $key, $value, array &$segments): void { - if (! property_exists($this->data, $key)) - return; - if ($this->data->{$key} instanceof MixtureFlag) { - $this->node_l->_getSegmentsOfGivenKeyAndValue($key, $value, $segments); - $this->node_r->_getSegmentsOfGivenKeyAndValue($key, $value, $segments); - } elseif ($this->isEqual($this->data->{$key}, $value)) { - $l = count($segments); - if ($l === 0) { - $segments[] = [ - $this->l, - $this->r - ]; - } else { - $end = &$segments[$l - 1]; - if (end($end) === $this->l - 1) - $end[1] = $this->r; - else - $segments[] = [ - $this->l, - $this->r - ]; - } - } - } - protected function _getSegmentsOfGivenKey(string $key, array &$segments): void { - if (! property_exists($this->data, $key)) - return; - if ($this->data->{$key} instanceof MixtureFlag) { - $this->node_l->_getSegmentsOfGivenKey($key, $segments); - $this->node_r->_getSegmentsOfGivenKey($key, $segments); - } else { - $l = count($segments); - if ($l === 0) { - $segments[] = [ - $this->l, - $this->r, - $this->data->{$key} - ]; - } else { - $end = &$segments[$l - 1]; - if ($end[1] === $this->l - 1 && $this->isEqual($this->data->{$key}, end($end))) - $end[1] = $this->r; - else - $segments[] = [ - $this->l, - $this->r, - $this->data->{$key} - ]; - } - } - } - protected function _getFixedArray(string $key, \SplFixedArray $fixed_array, int $offset): void { - if (! property_exists($this->data, $key)) { - for($i = $this->l; $i <= $this->r; ++ $i) - $fixed_array[$i - $offset] = new SegmentTreeValueNotFoundException(); - return; - } - $value = $this->data->{$key}; - if ($value instanceof MixtureFlag) { - $this->node_l->_getFixedArray($key, $fixed_array, $offset); - $this->node_r->_getFixedArray($key, $fixed_array, $offset); - } else { - for($i = $this->l; $i <= $this->r; ++ $i) - $fixed_array[$i - $offset] = $value; - } - } - protected function _setValue(int $l, int $r, string $key, $value): void { - if (property_exists($this->data, $key)) { - $current_value = $this->data->{$key}; - if ($this->isEqual($current_value, $value)) - return; - } - - if ($l <= $this->l && $this->r <= $r) { - $this->data->{$key} = $value; - } else { - $middle = $this->getMiddle(); - $this->createChildNode($middle); - if (property_exists($this->data, $key) && ! $current_value instanceof MixtureFlag) { - $this->node_l->data->{$key} = $current_value; - $this->node_r->data->{$key} = $current_value; - } - - if ($l <= $middle) - $this->node_l->_setValue($l, $r, $key, $value); - - if ($r > $middle) - $this->node_r->_setValue($l, $r, $key, $value); - - if (property_exists($this->node_l->data, $key) && property_exists($this->node_r->data, $key) && - $this->isEqual($this->node_l->data->{$key}, $value) && - $this->isEqual($this->node_r->data->{$key}, $value)) { - $this->data->{$key} = $value; - } else { - $this->data->{$key} = MixtureFlag::getInstance(); - } - } - } - protected function _delValue(int $l, int $r, string $key): void { - if (! property_exists($this->data, $key)) - return; - - if ($l <= $this->l && $this->r <= $r) { - unset($this->data->{$key}); - } else { - $middle = $this->getMiddle(); - $this->createChildNode($middle); - $current_value = $this->data->{$key}; - if (! $current_value instanceof MixtureFlag) { - $this->node_l->data->{$key} = $current_value; - $this->node_r->data->{$key} = $current_value; - } - - if ($l <= $middle) - $this->node_l->_delValue($l, $r, $key); - - if ($r > $middle) - $this->node_r->_delValue($l, $r, $key); - - if (property_exists($this->node_l->data, $key) || property_exists($this->node_r->data, $key)) { - $this->data->{$key} = MixtureFlag::getInstance(); - } else - unset($this->data->{$key}); - } - } - protected function createChildNode(int $middle): void { - if (! $this->hasChildNode()) { - $this->node_l = new static($this->l, $middle); - $this->node_r = new static($middle + 1, $this->r); - } - } +l = $l; + $this->r = $r; + $this->data = new \stdClass(); + } + public function count() { + return $this->r - $this->l + 1; + } + protected function hasChildNode(): bool { + return isset($this->node_l); + } + protected function isLeafNode(): bool { + return $this->l === $this->r; + } + protected function getMiddle(): int { + return intdiv($this->l + $this->r, 2); + } + protected function _getValue(int $position, string $key) { + if (! property_exists($this->data, $key)) { + throw new SegmentTreeValueNotFoundException(); + } + $value = $this->data->{$key}; + if ($value instanceof MixtureFlag) { + if ($position <= $this->getMiddle()) { + return $this->node_l->_getValue($position, $key); + } else { + return $this->node_r->_getValue($position, $key); + } + } else { + return $value; + } + } + protected function _getData(int $position, array &$result): void { + foreach ($this->data as $key => $value) + if ($value instanceof MixtureFlag) { + if ($position <= $this->getMiddle()) { + $this->node_l->_getData($position, $result); + } else { + $this->node_r->_getData($position, $result); + } + } else { + $result[$key] = $value; + } + } + protected function _exists(int $l, int $r, string $key): bool { + if (! property_exists($this->data, $key)) { + return false; + } + if (! $this->data->{$key} instanceof MixtureFlag) { + return true; + } + $middle = $this->getMiddle(); + if ($l <= $middle && $this->node_l->_exists($l, $r, $key)) { + return true; + } + if ($r > $middle && $this->node_r->_exists($l, $r, $key)) { + return true; + } + return false; + } + protected function _getSegmentsOfGivenKeyAndValue(string $key, $value, array &$segments): void { + if (! property_exists($this->data, $key)) { + return; + } + if ($this->data->{$key} instanceof MixtureFlag) { + $this->node_l->_getSegmentsOfGivenKeyAndValue($key, $value, $segments); + $this->node_r->_getSegmentsOfGivenKeyAndValue($key, $value, $segments); + } elseif ($this->isEqual($this->data->{$key}, $value)) { + $l = count($segments); + if ($l === 0) { + $segments[] = [ + $this->l, + $this->r + ]; + } else { + $end = &$segments[$l - 1]; + if (end($end) === $this->l - 1) { + $end[1] = $this->r; + } else { + $segments[] = [ + $this->l, + $this->r + ]; + } + } + } + } + protected function _getSegmentsOfGivenKey(string $key, array &$segments): void { + if (! property_exists($this->data, $key)) { + return; + } + if ($this->data->{$key} instanceof MixtureFlag) { + $this->node_l->_getSegmentsOfGivenKey($key, $segments); + $this->node_r->_getSegmentsOfGivenKey($key, $segments); + } else { + $l = count($segments); + if ($l === 0) { + $segments[] = [ + $this->l, + $this->r, + $this->data->{$key} + ]; + } else { + $end = &$segments[$l - 1]; + if ($end[1] === $this->l - 1 && $this->isEqual($this->data->{$key}, end($end))) { + $end[1] = $this->r; + } else { + $segments[] = [ + $this->l, + $this->r, + $this->data->{$key} + ]; + } + } + } + } + protected function _getFixedArray(string $key, \SplFixedArray $fixed_array, int $offset): void { + if (! property_exists($this->data, $key)) { + for ($i = $this->l; $i <= $this->r; ++$i) + $fixed_array[$i - $offset] = new SegmentTreeValueNotFoundException(); + return; + } + $value = $this->data->{$key}; + if ($value instanceof MixtureFlag) { + $this->node_l->_getFixedArray($key, $fixed_array, $offset); + $this->node_r->_getFixedArray($key, $fixed_array, $offset); + } else { + for ($i = $this->l; $i <= $this->r; ++$i) + $fixed_array[$i - $offset] = $value; + } + } + protected function _setValue(int $l, int $r, string $key, $value): void { + if (property_exists($this->data, $key)) { + $current_value = $this->data->{$key}; + if ($this->isEqual($current_value, $value)) { + return; + } + } + if ($l <= $this->l && $this->r <= $r) { + $this->data->{$key} = $value; + } else { + $middle = $this->getMiddle(); + $this->createChildNode($middle); + if (property_exists($this->data, $key) && ! $current_value instanceof MixtureFlag) { + $this->node_l->data->{$key} = $current_value; + $this->node_r->data->{$key} = $current_value; + } + if ($l <= $middle) { + $this->node_l->_setValue($l, $r, $key, $value); + } + if ($r > $middle) { + $this->node_r->_setValue($l, $r, $key, $value); + } + if (property_exists($this->node_l->data, $key) && property_exists($this->node_r->data, $key) && + $this->isEqual($this->node_l->data->{$key}, $value) && + $this->isEqual($this->node_r->data->{$key}, $value)) { + $this->data->{$key} = $value; + unset($this->node_l->data->{$key}); + unset($this->node_r->data->{$key}); + } else { + $this->data->{$key} = MixtureFlag::getInstance(); + } + } + } + protected function _delValue(int $l, int $r, string $key): void { + if (! property_exists($this->data, $key)) { + return; + } + if ($l <= $this->l && $this->r <= $r) { + unset($this->data->{$key}); + } else { + $middle = $this->getMiddle(); + $this->createChildNode($middle); + $current_value = $this->data->{$key}; + if (! $current_value instanceof MixtureFlag) { + $this->node_l->data->{$key} = $current_value; + $this->node_r->data->{$key} = $current_value; + } + if ($l <= $middle) { + $this->node_l->_delValue($l, $r, $key); + } + if ($r > $middle) { + $this->node_r->_delValue($l, $r, $key); + } + if (property_exists($this->node_l->data, $key) || property_exists($this->node_r->data, $key)) { + $this->data->{$key} = MixtureFlag::getInstance(); + } else { + unset($this->data->{$key}); + } + } + } + protected function createChildNode(int $middle): void { + if (! $this->hasChildNode()) { + $this->node_l = new static($this->l, $middle); + $this->node_r = new static($middle + 1, $this->r); + } + } } \ No newline at end of file diff --git a/src/SegmentTreeValueNotFoundException.php b/src/SegmentTreeValueNotFoundException.php index 66b1d36..6521fab 100644 --- a/src/SegmentTreeValueNotFoundException.php +++ b/src/SegmentTreeValueNotFoundException.php @@ -1,3 +1,4 @@ - $r) - throw new \Exception('Left position cannot be larger than right'); - return new self($l, $r); - } - /** - * - * @param string $key - * @param mixed $value - * @return array [[$seg1_l, $seg1_r], [$seg2_l, $seg2_r], [$seg3_l, $seg3_r], ...] - */ - public function getSegmentsOfGivenKeyAndValue(string $key, $value): array { - $segments = []; - $this->_getSegmentsOfGivenKeyAndValue($key, $value, $segments); - return $segments; - } - /** - * - * @param string $key - * @param mixed $value - * @return array [[$seg1_l, $seg1_r, $value1], [$seg2_l, $seg2_r, $value2], [$seg3_l, $seg3_r, $value3], ...] - */ - public function getSegmentsOfGivenKey(string $key): array { - $segments = []; - $this->_getSegmentsOfGivenKey($key, $segments); - return $segments; - } - /** - * Get a fixed array the size of whole tree filled with values of every position. - * The fixed array start with zero and values \Swango\SegmentTree\SegmentTreeValueNotFoundException for every empty position. - * - * @param string $key - * @return \SplFixedArray - */ - public function toFixedArray(string $key): \SplFixedArray { - $fixed_array = new \SplFixedArray($this->r - $this->l + 1); - $this->_getFixedArray($key, $fixed_array, $this->l); - return $fixed_array; - } - /** - * Fill $key between $l and $r by $value - * - * @param int $l - * @param int $r - * @param string $key - * @param mixed $value - * @throws \OutOfRangeException - * @return self - */ - public function setValue(int $l, int $r, string $key, $value): self { - if ($l > $r || $l < $this->l || $r > $this->r) - throw new \OutOfRangeException("Position out of range! l:$l,r:$r out of [$this->l, $this->r]"); - $this->_setValue($l, $r, $key, $value); - return $this; - } - /** - * - * Delete $key between $l and $r by $value. Noted it's different from setValue($l, $r, $key, null) - * - * @param int $l - * @param int $r - * @param string $key - * @throws \OutOfRangeException - * @return self - */ - public function delValue(int $l, int $r, string $key): self { - if ($l > $r || $l < $this->l || $r > $this->r) - throw new \OutOfRangeException("Position out of range! l:$l,r:$r out of [$this->l, $this->r]"); - $this->_delValue($l, $r, $key); - return $this; - } - /** - * - * @param int $position - * @param string $key - * @throws \Swango\SegmentTree\SegmentTreeValueNotFoundException Thorws when the given key is not set on $position - * @throws \OutOfRangeException - * @return mixed - */ - public function getValue(int $position, string $key) { - if ($position < $this->l || $position > $this->r) - throw new \OutOfRangeException("Position out of range! position:$position out of [$this->l, $this->r]"); - return $this->_getValue($position, $key); - } - /** - * determin if the given $key exists between $l and $r, no matter the value - * - * @param string $l - * @param string $r - * @param string $key - * @throws \OutOfRangeException - * @return bool - */ - public function exists(int $l, int $r, string $key): bool { - if ($l > $r || $l < $this->l || $r > $this->r) - throw new \OutOfRangeException("Position out of range! l:$l,r:$r out of [$this->l, $this->r]"); - return $this->_exists($l, $r, $key); - } - /** - * Get the left position and right position of the tree - * - * @return array [$l, $r] - */ - public function getLAndR(): array { - return [ - $this->l, - $this->r - ]; - } + $r) { + throw new \Exception('Left position cannot be larger than right'); + } + return new self($l, $r); + } + /** + * + * @param string $key + * @param mixed $value + * @return array [[$seg1_l, $seg1_r], [$seg2_l, $seg2_r], [$seg3_l, $seg3_r], ...] + */ + public function getSegmentsOfGivenKeyAndValue(string $key, $value): array { + $segments = []; + $this->_getSegmentsOfGivenKeyAndValue($key, $value, $segments); + return $segments; + } + /** + * + * @param string $key + * @param mixed $value + * @return array [[$seg1_l, $seg1_r, $value1], [$seg2_l, $seg2_r, $value2], [$seg3_l, $seg3_r, $value3], ...] + */ + public function getSegmentsOfGivenKey(string $key): array { + $segments = []; + $this->_getSegmentsOfGivenKey($key, $segments); + return $segments; + } + /** + * Get a fixed array the size of whole tree filled with values of every position. + * The fixed array start with zero and values \Swango\SegmentTree\SegmentTreeValueNotFoundException for every empty position. + * + * @param string $key + * @return \SplFixedArray + */ + public function toFixedArray(string $key): \SplFixedArray { + $fixed_array = new \SplFixedArray($this->r - $this->l + 1); + $this->_getFixedArray($key, $fixed_array, $this->l); + return $fixed_array; + } + /** + * Fill $key between $l and $r by $value + * + * @param int $l + * @param int $r + * @param string $key + * @param mixed $value + * @return self + * @throws \OutOfRangeException + */ + public function setValue(int $l, int $r, string $key, $value): self { + if ($l > $r || $l < $this->l || $r > $this->r) { + throw new \OutOfRangeException("Position out of range! l:$l,r:$r out of [$this->l, $this->r]"); + } + $this->_setValue($l, $r, $key, $value); + return $this; + } + /** + * + * Delete $key between $l and $r by $value. Noted it's different from setValue($l, $r, $key, null) + * + * @param int $l + * @param int $r + * @param string $key + * @return self + * @throws \OutOfRangeException + */ + public function delValue(int $l, int $r, string $key): self { + if ($l > $r || $l < $this->l || $r > $this->r) { + throw new \OutOfRangeException("Position out of range! l:$l,r:$r out of [$this->l, $this->r]"); + } + $this->_delValue($l, $r, $key); + return $this; + } + /** + * + * @param int $position + * @param string $key + * @return mixed + * @throws \OutOfRangeException + * @throws \Swango\SegmentTree\SegmentTreeValueNotFoundException Throws when the given key is not set on $position + */ + public function getValue(int $position, string $key) { + if ($position < $this->l || $position > $this->r) { + throw new \OutOfRangeException("Position out of range! position:$position out of [$this->l, $this->r]"); + } + return $this->_getValue($position, $key); + } + /** + * @param int $position + * @return array + * @throws \OutOfRangeException + */ + public function getAllValue(int $position): array { + if ($position < $this->l || $position > $this->r) { + throw new \OutOfRangeException("Position out of range! position:$position out of [$this->l, $this->r]"); + } + $result = []; + $this->_getData($position, $result); + return $result; + } + /** + * Determine if the given $key exists between $l and $r, no matter the value + * + * @param string $l + * @param string $r + * @param string $key + * @return bool + * @throws \OutOfRangeException + */ + public function exists(int $l, int $r, string $key): bool { + if ($l > $r || $l < $this->l || $r > $this->r) { + throw new \OutOfRangeException("Position out of range! l:$l,r:$r out of [$this->l, $this->r]"); + } + return $this->_exists($l, $r, $key); + } + /** + * Get the left position and right position of the tree + * + * @return array [$l, $r] + */ + public function getLAndR(): array { + return [ + $this->l, + $this->r + ]; + } } diff --git a/src/Tree/Date.php b/src/Tree/Date.php index 0103502..d9aeb09 100644 --- a/src/Tree/Date.php +++ b/src/Tree/Date.php @@ -1,183 +1,209 @@ -date_offset = $date_offset; - return $root; - } - protected static function convertDate($date, int $date_offset): int { - if (is_string($date)) - return intdiv(strtotime($date), 86400) - $date_offset; - else - return intdiv($date, 86400) - $date_offset; - } - protected static function toStringDate(int $position, int $date_offset): string { - return date('Y-m-d', ($position + $date_offset) * 86400); - } - /** - * - * @param string $key - * @param mixed $value - * @return array [[$seg1_l, $seg1_r], [$seg2_l, $seg2_r], [$seg3_l, $seg3_r], ...] - */ - public function getSegmentsOfGivenKeyAndValue(string $key, $value): array { - $segments = []; - $this->_getSegmentsOfGivenKeyAndValue($key, $value, $segments); - foreach ($segments as &$v) { - $l = reset($v); - $r = end($v); - $v = [ - self::toStringDate($l, $this->date_offset), - self::toStringDate($r, $this->date_offset) - ]; - } - return $segments; - } - /** - * - * @param string $key - * @param mixed $value - * @return array [[$seg1_l, $seg1_r, $value1], [$seg2_l, $seg2_r, $value2], [$seg3_l, $seg3_r, $value3], ...] - */ - public function getSegmentsOfGivenKey(string $key): array { - $segments = []; - $this->_getSegmentsOfGivenKey($key, $segments); - foreach ($segments as &$v) { - $l = reset($v); - $r = $v[1]; - $v = [ - self::toStringDate($l, $this->date_offset), - self::toStringDate($r, $this->date_offset), - end($v) - ]; - } - return $segments; - } - /** - * Get a fixed array the size of whole tree filled with values of every position. - * The fixed array start with zero and values \Swango\SegmentTree\SegmentTreeValueNotFoundException for every empty position. - * - * @param string $key - * @return array - */ - public function toArray(string $key): array { - $fixed_array = new \SplFixedArray($this->r - $this->l + 1); - $this->_getFixedArray($key, $fixed_array, $this->l); - $ret = []; - for($i = 0, $l = $fixed_array->getSize(); $i < $l; ++ $i) - $ret[self::toStringDate($i, $this->date_offset)] = $fixed_array[$i]; - return $ret; - } - /** - * Fill $key between $from_date and $to_date by $value - * - * @param mixed $from_date - * @param mixed $to_date - * @param string $key - * @param mixed $value - * @throws \OutOfRangeException - * @return self - */ - public function setValue($from_date, $to_date, string $key, $value): self { - if (isset($from_date)) - $l = self::convertDate($from_date, $this->date_offset); - else - $l = $this->l; - if (isset($to_date)) - $r = self::convertDate($to_date, $this->date_offset); - else - $r = $this->r; - if ($l > $r || $l < $this->l || $r > $this->r) - throw new \OutOfRangeException("Position out of range! l:$l,r:$r out of [$this->l, $this->r]"); - $this->_setValue($l, $r, $key, $value); - return $this; - } - /** - * - * Delete $key between $from_date and $to_date by $value. Noted it's different from setValue($from_date, $to_date, $key, null) - * - * @param mixed $from_date - * @param mixed $to_date - * @param string $key - * @throws \OutOfRangeException - * @return self - */ - public function delValue($from_date, $to_date, string $key): self { - if (isset($from_date)) - $l = self::convertDate($from_date, $this->date_offset); - else - $l = $this->l; - if (isset($to_date)) - $r = self::convertDate($to_date, $this->date_offset); - else - $r = $this->r; - if ($l > $r || $l < $this->l || $r > $this->r) - throw new \OutOfRangeException("Position out of range! l:$l,r:$r out of [$this->l, $this->r]"); - $this->_delValue($l, $r, $key); - return $this; - } - /** - * - * @param mixed $date - * @param string $key - * @throws \Swango\SegmentTree\SegmentTreeValueNotFoundException Thorws when the given key is not set on $position - * @throws \OutOfRangeException - * @return mixed - */ - public function getValue($date, string $key) { - $position = self::convertDate($date, $this->date_offset); - if ($position < $this->l || $position > $this->r) - throw new \OutOfRangeException("Position out of range! position:$position out of [$this->l, $this->r]"); - return $this->_getValue($position, $key); - } - /** - * determin if the given $key exists between $from_date and $to_date, no matter the value - * - * @param mixed $from_date - * @param mixed $to_date - * @param string $key - * @throws \OutOfRangeException - * @return bool - */ - public function exists($from_date, $to_date, string $key): bool { - if (isset($from_date)) - $l = self::convertDate($from_date, $this->date_offset); - else - $l = $this->l; - if (isset($to_date)) - $r = self::convertDate($to_date, $this->date_offset); - else - $r = $this->r; - if ($l > $r || $l < $this->l || $r > $this->r) - throw new \OutOfRangeException("Position out of range! l:$l,r:$r out of [$this->l, $this->r]"); - return $this->_exists($l, $r, $key); - } - /** - * Get the min date and max date of the tree - * - * @return array [$l, $r] - */ - public function getMinDateAndMaxDate(): array { - return [ - self::toStringDate($this->l, $this->date_offset), - self::toStringDate($this->r, $this->date_offset) - ]; - } +date_offset = $date_offset; + return $root; + } + protected static function convertDate($date, int $date_offset): int { + if (is_string($date)) { + return intdiv(strtotime($date), 86400) - $date_offset; + } else { + return intdiv($date, 86400) - $date_offset; + } + } + protected static function toStringDate(int $position, int $date_offset): string { + return date('Y-m-d', ($position + $date_offset) * 86400); + } + /** + * + * @param string $key + * @param mixed $value + * @return array [[$seg1_l, $seg1_r], [$seg2_l, $seg2_r], [$seg3_l, $seg3_r], ...] + */ + public function getSegmentsOfGivenKeyAndValue(string $key, $value): array { + $segments = []; + $this->_getSegmentsOfGivenKeyAndValue($key, $value, $segments); + foreach ($segments as &$v) { + $l = reset($v); + $r = end($v); + $v = [ + self::toStringDate($l, $this->date_offset), + self::toStringDate($r, $this->date_offset) + ]; + } + return $segments; + } + /** + * + * @param string $key + * @param mixed $value + * @return array [[$seg1_l, $seg1_r, $value1], [$seg2_l, $seg2_r, $value2], [$seg3_l, $seg3_r, $value3], ...] + */ + public function getSegmentsOfGivenKey(string $key): array { + $segments = []; + $this->_getSegmentsOfGivenKey($key, $segments); + foreach ($segments as &$v) { + $l = reset($v); + $r = $v[1]; + $v = [ + self::toStringDate($l, $this->date_offset), + self::toStringDate($r, $this->date_offset), + end($v) + ]; + } + return $segments; + } + /** + * Get a fixed array the size of whole tree filled with values of every position. + * The fixed array start with zero and values \Swango\SegmentTree\SegmentTreeValueNotFoundException for every empty position. + * + * @param string $key + * @return array + */ + public function toArray(string $key): array { + $fixed_array = new \SplFixedArray($this->r - $this->l + 1); + $this->_getFixedArray($key, $fixed_array, $this->l); + $ret = []; + for ($i = 0, $l = $fixed_array->getSize(); $i < $l; ++$i) + $ret[self::toStringDate($i, $this->date_offset)] = $fixed_array[$i]; + return $ret; + } + /** + * Fill $key between $from_date and $to_date by $value + * + * @param mixed $from_date + * @param mixed $to_date + * @param string $key + * @param mixed $value + * @return self + * @throws \OutOfRangeException + */ + public function setValue($from_date, $to_date, string $key, $value): self { + if (isset($from_date)) { + $l = self::convertDate($from_date, $this->date_offset); + } else { + $l = $this->l; + } + if (isset($to_date)) { + $r = self::convertDate($to_date, $this->date_offset); + } else { + $r = $this->r; + } + if ($l > $r || $l < $this->l || $r > $this->r) { + throw new \OutOfRangeException("Position out of range! l:$l,r:$r out of [$this->l, $this->r]"); + } + $this->_setValue($l, $r, $key, $value); + return $this; + } + /** + * + * Delete $key between $from_date and $to_date by $value. Noted it's different from setValue($from_date, $to_date, $key, null) + * + * @param mixed $from_date + * @param mixed $to_date + * @param string $key + * @return self + * @throws \OutOfRangeException + */ + public function delValue($from_date, $to_date, string $key): self { + if (isset($from_date)) { + $l = self::convertDate($from_date, $this->date_offset); + } else { + $l = $this->l; + } + if (isset($to_date)) { + $r = self::convertDate($to_date, $this->date_offset); + } else { + $r = $this->r; + } + if ($l > $r || $l < $this->l || $r > $this->r) { + throw new \OutOfRangeException("Position out of range! l:$l,r:$r out of [$this->l, $this->r]"); + } + $this->_delValue($l, $r, $key); + return $this; + } + /** + * + * @param mixed $date + * @param string $key + * @return mixed + * @throws \OutOfRangeException + * @throws \Swango\SegmentTree\SegmentTreeValueNotFoundException Throws when the given key is not set on $position + */ + public function getValue($date, string $key) { + $position = self::convertDate($date, $this->date_offset); + if ($position < $this->l || $position > $this->r) { + throw new \OutOfRangeException("Position out of range! position:$position out of [$this->l, $this->r]"); + } + return $this->_getValue($position, $key); + } + /** + * @param mixed $date + * @return array + * @throws \OutOfRangeException + */ + public function getAllValue($date): array { + $position = self::convertDate($date, $this->date_offset); + if ($position < $this->l || $position > $this->r) { + throw new \OutOfRangeException("Position out of range! position:$position out of [$this->l, $this->r]"); + } + $result = []; + $this->_getData($position, $result); + return $result; + } + /** + * Determine if the given $key exists between $from_date and $to_date, no matter the value + * + * @param mixed $from_date + * @param mixed $to_date + * @param string $key + * @return bool + * @throws \OutOfRangeException + */ + public function exists($from_date, $to_date, string $key): bool { + if (isset($from_date)) { + $l = self::convertDate($from_date, $this->date_offset); + } else { + $l = $this->l; + } + if (isset($to_date)) { + $r = self::convertDate($to_date, $this->date_offset); + } else { + $r = $this->r; + } + if ($l > $r || $l < $this->l || $r > $this->r) { + throw new \OutOfRangeException("Position out of range! l:$l,r:$r out of [$this->l, $this->r]"); + } + return $this->_exists($l, $r, $key); + } + /** + * Get the min date and max date of the tree + * + * @return array [$l, $r] + */ + public function getMinDateAndMaxDate(): array { + return [ + self::toStringDate($this->l, $this->date_offset), + self::toStringDate($this->r, $this->date_offset) + ]; + } } \ No newline at end of file diff --git a/src/Tree/Version.php b/src/Tree/Version.php index 3169a95..0ba2575 100644 --- a/src/Tree/Version.php +++ b/src/Tree/Version.php @@ -1,255 +1,280 @@ -$v) - $compressed_version_map[$i] = $v; - self::qsortVersions($compressed_version_map, 0, $length - 1); - $version_compressed_map = []; - for($i = 0; $i < $length; ++ $i) - $version_compressed_map[$compressed_version_map[$i]] = $i; - - $root = new self(0, $length); - $root->compressed_version_map = $compressed_version_map; - $root->version_compressed_map = $version_compressed_map; - return $root; - } - /** - * Create a tree by given sorted versions. - * - * @param \SplFixedArray $versions - * @return Version - */ - public static function newTreeWithSortedVersions(string ...$version): Version { - $versions = array_unique($version); - $length = count($versions); - $compressed_version_map = new \SplFixedArray($length - 1); - foreach ($versions as $i=>$v) - $compressed_version_map[$i] = $v; - $version_compressed_map = []; - for($i = 0; $i < $length; ++ $i) - $version_compressed_map[$compressed_version_map[$i]] = $i; - - $root = new self(0, $length); - $root->compressed_version_map = $compressed_version_map; - $root->version_compressed_map = $version_compressed_map; - return $root; - } - /** - * Quick sort - * - * @param \SplFixedArray $arr - * @param int $l - * @param int $r - */ - protected static function qsortVersions(\SplFixedArray $arr, int $l, int $r) { - $i = $l; - $j = $r; - $x = $arr[intdiv($l + $r, 2)]; - $t = null; - do { - while ( version_compare($arr[$i], $x) === - 1 ) - ++ $i; - while ( version_compare($x, $arr[$j]) === - 1 ) - -- $j; - if ($i <= $j) { - $t = $arr[$i]; - $arr[$i] = $arr[$j]; - $arr[$j] = $t; - ++ $i; - -- $j; - } - } while ( $i <= $j ); - unset($t); - unset($x); - if ($i < $r) - self::qsortVersions($arr, $i, $r); - if ($l < $j) - self::qsortVersions($arr, $l, $j); - } - /** - * - * @param string $key - * @param mixed $value - * @return array [[$seg1_l, $seg1_r], [$seg2_l, $seg2_r], [$seg3_l, $seg3_r], ...] - */ - public function getSegmentsOfGivenKeyAndValue(string $key, $value): array { - $segments = []; - $this->_getSegmentsOfGivenKeyAndValue($key, $value, $segments); - foreach ($segments as &$v) { - $min_version = reset($v); - $max_version = end($v); - $v = [ - $this->compressed_version_map[$min_version], - $this->compressed_version_map[$max_version] - ]; - } - return $segments; - } - /** - * - * @param string $key - * @param mixed $value - * @return array [[$seg1_l, $seg1_r, $value1], [$seg2_l, $seg2_r, $value2], [$seg3_l, $seg3_r, $value3], ...] - */ - public function getSegmentsOfGivenKey(string $key): array { - $segments = []; - $this->_getSegmentsOfGivenKey($key, $segments); - foreach ($segments as &$v) { - $min_version = reset($v); - $max_version = $v[1]; - $v = [ - $this->compressed_version_map[$min_version], - $this->compressed_version_map[$max_version], - end($v) - ]; - } - return $segments; - } - /** - * Get a fixed array the size of whole tree filled with values of every position. - * The fixed array start with zero and values \Swango\SegmentTree\SegmentTreeValueNotFoundException for every empty position. - * - * @param string $key - * @return array - */ - public function toArray(string $key): array { - $fixed_array = new \SplFixedArray($this->r - $this->l + 1); - $this->_getFixedArray($key, $fixed_array, $this->l); - $ret = []; - for($i = 0, $l = $fixed_array->getSize(); $i < $l; ++ $i) - $ret[$this->compressed_version_map[$i]] = $fixed_array[$i]; - return $ret; - } - /** - * Fill $key between $from_version and $to_version by $value - * - * @param string $from_version - * @param string $to_version - * @param string $key - * @param mixed $value - * @throws \OutOfRangeException - * @return self - */ - public function setValue(?string $from_version, ?string $to_version, string $key, $value): self { - if (isset($from_version)) - $l = $this->version_compressed_map[$from_version]; - else - $l = $this->l; - if (isset($to_version)) - $r = $this->version_compressed_map[$to_version]; - else - $r = $this->r; - if ($l > $r || $l < $this->l || $r > $this->r) - throw new \OutOfRangeException("Position out of range! l:$l,r:$r out of [$this->l, $this->r]"); - $this->_setValue($l, $r, $key, $value); - return $this; - } - /** - * - * Delete $key between $from_version and $to_version by $value. Noted it's different from setValue($from_version, $to_version, $key, null) - * - * @param string $from_version - * @param string $to_version - * @param string $key - * @throws \OutOfRangeException - * @return self - */ - public function delValue(?string $from_version, ?string $to_version, string $key): self { - if (isset($from_version)) - $l = $this->version_compressed_map[$from_version]; - else - $l = $this->l; - if (isset($to_version)) - $r = $this->version_compressed_map[$to_version]; - else - $r = $this->r; - if ($l > $r || $l < $this->l || $r > $this->r) - throw new \OutOfRangeException("Position out of range! l:$l,r:$r out of [$this->l, $this->r]"); - $this->_delValue($l, $r, $key); - return $this; - } - /** - * - * @param string $version - * @param string $key - * @throws \Swango\SegmentTree\SegmentTreeValueNotFoundException Thorws when the given key is not set on $position - * @throws \OutOfRangeException - * @return mixed - */ - public function getValue(string $version, string $key) { - $position = $this->version_compressed_map[$version]; - if ($position < $this->l || $position > $this->r) - throw new \OutOfRangeException("Position out of range! position:$position out of [$this->l, $this->r]"); - return $this->_getValue($position, $key); - } - /** - * determin if the given $key exists between $from_version and $to_version, no matter the value - * - * @param string $from_version - * @param string $to_version - * @param string $key - * @throws \OutOfRangeException - * @return bool - */ - public function exists(?string $from_version, ?string $to_version, string $key): bool { - if (isset($from_version)) - $l = $this->version_compressed_map[$from_version]; - else - $l = $this->l; - if (isset($to_version)) - $r = $this->version_compressed_map[$to_version]; - else - $r = $this->r; - if ($l > $r || $l < $this->l || $r > $this->r) - throw new \OutOfRangeException("Position out of range! l:$l,r:$r out of [$this->l, $this->r]"); - return $this->_exists($l, $r, $key); - } - /** - * Get the min version and max version of the tree - * - * @return array [$l, $r] - */ - public function getMinVersionAndMaxVersion(): array { - return [ - $this->compressed_version_map[$this->l], - $this->compressed_version_map[$this->r] - ]; - } - /** - * Determin if given version is valid - * - * @param string $version - * @return bool - */ - public function versionValid(string $version): bool { - return array_key_exists($version, $this->version_compressed_map); - } - /** - * - * @param string $version - * @return string|NULL - */ - public function getVersionBefore(string $version): ?string { - $version = $this->version_compressed_map[$version]; - if ($version === 0) - return null; - return $this->compressed_version_map[$version - 1]; - } + $v) + $compressed_version_map[$i] = $v; + self::qsortVersions($compressed_version_map, 0, $length - 1); + $version_compressed_map = []; + for ($i = 0; $i < $length; ++$i) + $version_compressed_map[$compressed_version_map[$i]] = $i; + $root = new self(0, $length); + $root->compressed_version_map = $compressed_version_map; + $root->version_compressed_map = $version_compressed_map; + return $root; + } + /** + * Create a tree by given sorted versions. + * + * @param \SplFixedArray $versions + * @return Version + */ + public static function newTreeWithSortedVersions(string ...$version): Version { + $versions = array_unique($version); + $length = count($versions); + $compressed_version_map = new \SplFixedArray($length - 1); + foreach ($versions as $i => $v) + $compressed_version_map[$i] = $v; + $version_compressed_map = []; + for ($i = 0; $i < $length; ++$i) + $version_compressed_map[$compressed_version_map[$i]] = $i; + $root = new self(0, $length); + $root->compressed_version_map = $compressed_version_map; + $root->version_compressed_map = $version_compressed_map; + return $root; + } + /** + * Quick sort + * + * @param \SplFixedArray $arr + * @param int $l + * @param int $r + */ + protected static function qsortVersions(\SplFixedArray $arr, int $l, int $r) { + $i = $l; + $j = $r; + $x = $arr[intdiv($l + $r, 2)]; + $t = null; + do { + while (version_compare($arr[$i], $x) === -1) + ++$i; + while (version_compare($x, $arr[$j]) === -1) + --$j; + if ($i <= $j) { + $t = $arr[$i]; + $arr[$i] = $arr[$j]; + $arr[$j] = $t; + ++$i; + --$j; + } + } while ($i <= $j); + unset($t); + unset($x); + if ($i < $r) { + self::qsortVersions($arr, $i, $r); + } + if ($l < $j) { + self::qsortVersions($arr, $l, $j); + } + } + /** + * + * @param string $key + * @param mixed $value + * @return array [[$seg1_l, $seg1_r], [$seg2_l, $seg2_r], [$seg3_l, $seg3_r], ...] + */ + public function getSegmentsOfGivenKeyAndValue(string $key, $value): array { + $segments = []; + $this->_getSegmentsOfGivenKeyAndValue($key, $value, $segments); + foreach ($segments as &$v) { + $min_version = reset($v); + $max_version = end($v); + $v = [ + $this->compressed_version_map[$min_version], + $this->compressed_version_map[$max_version] + ]; + } + return $segments; + } + /** + * + * @param string $key + * @param mixed $value + * @return array [[$seg1_l, $seg1_r, $value1], [$seg2_l, $seg2_r, $value2], [$seg3_l, $seg3_r, $value3], ...] + */ + public function getSegmentsOfGivenKey(string $key): array { + $segments = []; + $this->_getSegmentsOfGivenKey($key, $segments); + foreach ($segments as &$v) { + $min_version = reset($v); + $max_version = $v[1]; + $v = [ + $this->compressed_version_map[$min_version], + $this->compressed_version_map[$max_version], + end($v) + ]; + } + return $segments; + } + /** + * Get a fixed array the size of whole tree filled with values of every position. + * The fixed array start with zero and values \Swango\SegmentTree\SegmentTreeValueNotFoundException for every empty position. + * + * @param string $key + * @return array + */ + public function toArray(string $key): array { + $fixed_array = new \SplFixedArray($this->r - $this->l + 1); + $this->_getFixedArray($key, $fixed_array, $this->l); + $ret = []; + for ($i = 0, $l = $fixed_array->getSize(); $i < $l; ++$i) + $ret[$this->compressed_version_map[$i]] = $fixed_array[$i]; + return $ret; + } + /** + * Fill $key between $from_version and $to_version by $value + * + * @param string $from_version + * @param string $to_version + * @param string $key + * @param mixed $value + * @return self + * @throws \OutOfRangeException + */ + public function setValue(?string $from_version, ?string $to_version, string $key, $value): self { + if (isset($from_version)) { + $l = $this->version_compressed_map[$from_version]; + } else { + $l = $this->l; + } + if (isset($to_version)) { + $r = $this->version_compressed_map[$to_version]; + } else { + $r = $this->r; + } + if ($l > $r || $l < $this->l || $r > $this->r) { + throw new \OutOfRangeException("Position out of range! l:$l,r:$r out of [$this->l, $this->r]"); + } + $this->_setValue($l, $r, $key, $value); + return $this; + } + /** + * + * Delete $key between $from_version and $to_version by $value. Noted it's different from setValue($from_version, $to_version, $key, null) + * + * @param string $from_version + * @param string $to_version + * @param string $key + * @return self + * @throws \OutOfRangeException + */ + public function delValue(?string $from_version, ?string $to_version, string $key): self { + if (isset($from_version)) { + $l = $this->version_compressed_map[$from_version]; + } else { + $l = $this->l; + } + if (isset($to_version)) { + $r = $this->version_compressed_map[$to_version]; + } else { + $r = $this->r; + } + if ($l > $r || $l < $this->l || $r > $this->r) { + throw new \OutOfRangeException("Position out of range! l:$l,r:$r out of [$this->l, $this->r]"); + } + $this->_delValue($l, $r, $key); + return $this; + } + /** + * + * @param string $version + * @param string $key + * @return mixed + * @throws \OutOfRangeException + * @throws \Swango\SegmentTree\SegmentTreeValueNotFoundException Throws when the given key is not set on $position + */ + public function getValue(string $version, string $key) { + $position = $this->version_compressed_map[$version]; + if ($position < $this->l || $position > $this->r) { + throw new \OutOfRangeException("Position out of range! position:$position out of [$this->l, $this->r]"); + } + return $this->_getValue($position, $key); + } + /** + * @param string $version + * @return array + * @throws \OutOfRangeException + */ + public function getAllValue(string $version): array { + $position = $this->version_compressed_map[$version]; + if ($position < $this->l || $position > $this->r) { + throw new \OutOfRangeException("Position out of range! position:$position out of [$this->l, $this->r]"); + } + $result = []; + $this->_getData($position, $result); + return $result; + } + /** + * Determine if the given $key exists between $from_version and $to_version, no matter the value + * + * @param string $from_version + * @param string $to_version + * @param string $key + * @return bool + * @throws \OutOfRangeException + */ + public function exists(?string $from_version, ?string $to_version, string $key): bool { + if (isset($from_version)) { + $l = $this->version_compressed_map[$from_version]; + } else { + $l = $this->l; + } + if (isset($to_version)) { + $r = $this->version_compressed_map[$to_version]; + } else { + $r = $this->r; + } + if ($l > $r || $l < $this->l || $r > $this->r) { + throw new \OutOfRangeException("Position out of range! l:$l,r:$r out of [$this->l, $this->r]"); + } + return $this->_exists($l, $r, $key); + } + /** + * Get the min version and max version of the tree + * + * @return array [$l, $r] + */ + public function getMinVersionAndMaxVersion(): array { + return [ + $this->compressed_version_map[$this->l], + $this->compressed_version_map[$this->r] + ]; + } + /** + * Determine if given version is valid + * + * @param string $version + * @return bool + */ + public function versionValid(string $version): bool { + return array_key_exists($version, $this->version_compressed_map); + } + /** + * + * @param string $version + * @return string|NULL + */ + public function getVersionBefore(string $version): ?string { + $version = $this->version_compressed_map[$version]; + if ($version === 0) { + return null; + } + return $this->compressed_version_map[$version - 1]; + } } \ No newline at end of file