Skip to content

Commit 2193bd5

Browse files
committed
Added ArrayAccess interface to the rope with implementation and tests
Fixed a nasty bug in which the state of the given ropes in concatRope and splitRope were changed Added clone logic to the ropes Fixed a bug where inserting at the end of the rope would cause an error (added missing test for this) Added changeValue function to RopeNode Added more to the concat and split tests Updated a few rope functions to return explicit references updated readme with a quick rope example
1 parent 3145a38 commit 2193bd5

File tree

7 files changed

+277
-34
lines changed

7 files changed

+277
-34
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ use PhpTrees\BinarySearchTree;
2424
$b = new PhpTrees\BinarySearchTree(5);
2525
```
2626

27+
To use the Rope
28+
29+
```php
30+
use PhpTrees\Rope;
31+
32+
$r = new PhpTrees\Rope("This is a Rope!");
33+
```
34+
2735
This will create a binary search tree with a root of the value 5\
2836
For a more detailed description of the features of PHP Trees, check out the wiki\
2937
<https://github.com/CrimsonNynja/PHP-Trees/wiki>

src/PhpTrees/Rope.php

Lines changed: 91 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use PhpTrees\RopeNode;
66

7-
class Rope
7+
class Rope implements \ArrayAccess
88
{
99
/* the root of the tree */
1010
private $root = null;
@@ -49,7 +49,11 @@ public function length() : int
4949
*/
5050
public function insert(string $value, int $index = null) : void
5151
{
52-
if ($index > $this->length()) {
52+
if ($index === null) {
53+
$index = $this->length();
54+
}
55+
56+
if ($index >= $this->length()) {
5357
$r = concatRope($this, new Rope($value));
5458
}
5559
else {
@@ -93,9 +97,9 @@ public function index(int $index, RopeNode $node = null) : ?string
9397
* splits the given node at the given position into 2 nodes
9498
* @param int $index the index to split at
9599
* @param RopeNode $node the recursive node to search
96-
* @return RopeNode returns the location of the split (the new parent of the newly created nodes)
100+
* @return RopeNode returns a reference to the location of the split (the new parent of the newly created nodes)
97101
*/
98-
public function splitNodeAtPosition(int $index, RopeNode $node = null) : ?RopeNode
102+
public function &splitNodeAtPosition(int $index, RopeNode $node = null) : ?RopeNode
99103
{
100104
if ($node === null) {
101105
$node = $this->root;
@@ -212,7 +216,7 @@ public function getRoot() : ?RopeNode
212216
* @param RopeNode $node the node to recurse on
213217
* @return RopeNode the node of the given index
214218
*/
215-
private function getNodeOfIndex(int &$index, RopeNode $node = null) : ?RopeNode
219+
private function &getNodeOfIndex(int &$index, RopeNode $node = null) : ?RopeNode
216220
{
217221
if ($node === null) {
218222
$node = $this->root;
@@ -235,10 +239,6 @@ private function getNodeOfIndex(int &$index, RopeNode $node = null) : ?RopeNode
235239
return null;
236240
}
237241

238-
// THOUGHTS
239-
// this could implement PHP ArrayAccess interface (offsetExists, offsetGet, offsetSet, offsetUnset)
240-
// is there need to implement iterators here>
241-
242242
///////////////////////////////
243243
//php functions
244244
//////////////////////////////
@@ -251,4 +251,86 @@ public function __toString() : string
251251
{
252252
return $this->toString();
253253
}
254+
255+
/**
256+
* clones the current rope
257+
*/
258+
public function __clone()
259+
{
260+
if ($this->root !== null) {
261+
$this->root = clone $this->root;
262+
}
263+
}
264+
265+
/**
266+
* check if the given offset exists in the Rope
267+
* @param mixed $offset the offset to check for
268+
* @return bool true if the given offset exists
269+
*/
270+
public function offsetExists($offset) : bool
271+
{
272+
if (is_int($offset)) {
273+
if ($offset <= $this->length()) {
274+
return true;
275+
}
276+
return false;
277+
} else {
278+
throw new \Exception('Rope offset must be an integer');
279+
}
280+
}
281+
282+
/**
283+
* gets the index of the rope
284+
* @param mixed $offset the offset to check
285+
*/
286+
public function offsetGet($offset)
287+
{
288+
if (is_int($offset)) {
289+
return $this->index($offset);
290+
}
291+
else {
292+
throw new \Exception('Rope offset must be an integer');
293+
}
294+
}
295+
296+
/**
297+
*
298+
*/
299+
public function offsetSet($offset, $value) : void
300+
{
301+
if ($offset === null && is_string($value)) {
302+
$this->insert($value);
303+
return;
304+
}
305+
306+
if (is_int($offset)) {
307+
if (is_string($value) && strlen($value) === 1) {
308+
$index = $offset;
309+
$node = &$this->getNodeOfIndex($index);
310+
$v = substr_replace($node->getValue(), $value, $index, 1);
311+
$node->changeValue($v);
312+
313+
}
314+
else {
315+
throw new \Exception("Value must be a 1 char string");
316+
}
317+
}
318+
else {
319+
throw new \Exception('Rope offset must be an integer');
320+
}
321+
}
322+
323+
/**
324+
* removes the character from the given index
325+
* @param mixed $offset the offset to remove the character from
326+
*/
327+
public function offsetUnset($offset) : void
328+
{
329+
if (is_int($offset)) {
330+
$this->removeSubstr($offset, 1);
331+
}
332+
else {
333+
throw new \Exception('Rope offset must be an integer');
334+
}
335+
}
254336
}

src/PhpTrees/RopeFunctions.php

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ function concatRope(Rope $r1, Rope $r2) : Rope
1919
return $r1;
2020
}
2121

22+
$rCopy = clone $r1;
23+
$r2Copy = clone $r2;
2224
$ret = new Rope("");
23-
$ret->getRoot()->addLeftChild($r1->getRoot());
24-
$ret->getRoot()->addRightChild($r2->getRoot());
25+
$ret->getRoot()->addLeftChild($rCopy->getRoot());
26+
$ret->getRoot()->addRightChild($r2Copy->getRoot());
2527

2628
return $ret;
2729
}
@@ -38,12 +40,11 @@ function splitRope(Rope $rope, int $index) : array
3840
return [$rope];
3941
}
4042

41-
$r1 = $rope;
42-
$node = $r1->splitNodeAtPosition($index);
43-
$n = $node->removeRightChildren();
43+
$r1 = clone $rope;
44+
$cursor = &$r1->splitNodeAtPosition($index);
45+
$n = $cursor->removeRightChildren();
4446
$r2 = new Rope($n->getValue());
4547

46-
$cursor = $node;
4748
$lastCursor = null;
4849
while ($cursor !== null) {
4950
if ($cursor->getRightChild() !== null && $cursor->getRightChild() !== $lastCursor) {
@@ -53,13 +54,13 @@ function splitRope(Rope $rope, int $index) : array
5354
}
5455
else {
5556
$dummy = new Rope();
56-
$dummy->constructFromNode($node);
57+
$dummy->constructFromNode(clone $node);
5758
}
5859
$r2 = concatRope($r2, $dummy);
5960
}
6061

6162
$lastCursor = $cursor;
62-
$cursor = $cursor->getParent();
63+
$cursor = &$cursor->getParent();
6364
}
6465

6566
return [$r1, $r2];

src/PhpTrees/RopeNode.php

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ public function getWeight() : int
140140
* gets the parent of the node
141141
* @return RopeNode the nodes parent, null if it has none
142142
*/
143-
public function getParent() : ?RopeNode
143+
public function &getParent() : ?RopeNode
144144
{
145145
return $this->parent;
146146
}
@@ -171,4 +171,35 @@ public function setParent(?RopeNode $parent) : void
171171
{
172172
$this->parent = $parent;
173173
}
174+
175+
/**
176+
* changes the value of the node, if it is a leaf
177+
* @param string $newVal the new value for the node
178+
*/
179+
public function changeValue(string $newVal) : void
180+
{
181+
if ($this->hasChildren() === false) {
182+
$this->value = $newVal;
183+
}
184+
}
185+
186+
///////////////////////////////
187+
//php functions
188+
//////////////////////////////
189+
190+
/**
191+
* clones the current RopeNode
192+
*/
193+
public function __clone()
194+
{
195+
if ($this->left !== null) {
196+
$this->left = clone $this->left;
197+
$this->left->setParent($this);
198+
}
199+
200+
if ($this->right !== null) {
201+
$this->right = clone $this->right;
202+
$this->right->setParent($this);
203+
}
204+
}
174205
}

tests/PhpTrees/RopeFunctionsTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ public function testConcat()
1111
$r2 = new Rope("words2");
1212
$concat = concatRope($r, $r2);
1313

14+
$this->assertNull($r->getRoot()->getLeftChild());
15+
$this->assertNull($r->getRoot()->getRightChild());
16+
$this->assertNull($r->getRoot()->getParent());
17+
$this->assertNull($r2->getRoot()->getLeftChild());
18+
$this->assertNull($r2->getRoot()->getLeftChild());
19+
$this->assertNull($r->getRoot()->getParent());
20+
1421
$this->assertNull($concat->getRoot()->getValue());
1522
$this->assertSame($concat->getRoot()->getRightChild()->getValue(), "words2");
1623
$this->assertSame($concat->getRoot()->getLeftChild()->getValue(), "words 1");
@@ -21,16 +28,25 @@ public function testConcat()
2128
$r2 = new Rope();
2229
$concat = concatRope($r, $r2);
2330
$this->assertSame($r, $concat);
31+
$this->assertNull($r->getRoot()->getLeftChild());
32+
$this->assertNull($r->getRoot()->getRightChild());
33+
$this->assertNull($r->getRoot()->getParent());
34+
$this->assertNull($r2->getRoot());
2435

2536
$r = new Rope();
2637
$r2 = new Rope("words 2");
2738
$concat = concatRope($r, $r2);
2839
$this->assertSame($r2, $concat);
40+
$this->assertNull($r->getRoot());
41+
$this->assertNull($r2->getRoot()->getLeftChild());
42+
$this->assertNull($r2->getRoot()->getLeftChild());
2943

3044
$r = new Rope();
3145
$r2 = new Rope();
3246
$concat = concatRope($r, $r2);
3347
$this->assertSame($r, $concat);
48+
$this->assertNull($r->getRoot());
49+
$this->assertNull($r2->getRoot());
3450

3551
$r = new Rope("words 1");
3652
$r2 = new Rope("words2");
@@ -40,6 +56,10 @@ public function testConcat()
4056
$rope2 = concatRope($r, $r2);
4157
$rope = concatRope($rope1, $rope2);
4258
$this->assertSame($rope->toString(), "words 1words2words3words 4");
59+
$this->assertNull($r->getRoot()->getParent());
60+
$this->assertNull($r2->getRoot()->getParent());
61+
$this->assertNull($rope1->getRoot()->getParent());
62+
$this->assertNull($rope2->getRoot()->getParent());
4363
}
4464

4565
public function testSplit()

tests/PhpTrees/RopeNodeTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,16 @@ public function testSplitAndAddChildren()
118118
$this->assertNull($n->getValue());
119119
$this->assertSame($n->getWeight(), 5);
120120
}
121+
122+
public function testChangeValue()
123+
{
124+
$n = new PhpTrees\RopeNode("words");
125+
$n->changeValue("bob");
126+
$this->assertSame($n->getValue(), "bob");
127+
128+
$n = new PhpTrees\RopeNode("words");
129+
$n->addLeftChild(new PhpTrees\RopeNode("bob"));
130+
$n->changeValue("bob");
131+
$this->assertNull($n->getValue());
132+
}
121133
}

0 commit comments

Comments
 (0)