├── doc.go ├── rope_test.go ├── LICENSE ├── README.md └── rope.go /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package go-rope provides a persistent rope data structure for storing large texts. 3 | 4 | Refer to README.md for further information. 5 | 6 | Installation 7 | 8 | go get github.com/vinzmay/go-rope 9 | 10 | */ 11 | package rope 12 | -------------------------------------------------------------------------------- /rope_test.go: -------------------------------------------------------------------------------- 1 | package rope 2 | 3 | import "testing" 4 | 5 | //Test rope creation 6 | func TestRopeCreation(t *testing.T) { 7 | r := New("test") 8 | if r.String() != "test" { 9 | t.Error("Error creating rope - equality fail: ", r, " != test") 10 | } 11 | if r.Len() != 4 { 12 | t.Error("Error creating rope - length fail: ", r.Len(), "!= 4") 13 | } 14 | } 15 | 16 | //Test rope concatenation 17 | func TestRopeConcat(t *testing.T) { 18 | r := New("abcdef") 19 | r2 := New("ghilmno") 20 | r3 := r.Concat(r2) 21 | if r.String() != "abcdef" || r.Len() != 6 { 22 | t.Error("Error concatenating ropes, r modified:", r) 23 | } 24 | if r2.String() != "ghilmno" || r2.Len() != 7 { 25 | t.Error("Error concatenating ropes, r2 modified:", r2) 26 | } 27 | if r3.String() != "abcdefghilmno" || r3.Len() != 13 { 28 | t.Error("Error concatenating ropes, r3 not correct:", r3, "!= abcdefghilmno") 29 | } 30 | } 31 | 32 | //Test rope split 33 | func TestRopeSplit(t *testing.T) { 34 | r := New("abcdef") 35 | r1, r2 := r.Split(4) 36 | if r.String() != "abcdef" || r1.String() != "abcd" || r2.String() != "ef" { 37 | t.Error("Error splitting string: abcd/ef => ", r1, r2) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Vincenzo Maggio 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-rope v0.5 2 | ============ 3 | 4 | Description 5 | ----------- 6 | 7 | [Go](http://www.golang.org) implementation of a persistent [rope](http://en.wikipedia.org/wiki/Rope_%28data_structure%29) data structure, useful to manipulate large text. Persistent means that any operation on the rope doesn't modify it, so it's inherently thread safe. 8 | 9 | TODO: Rebalancing 10 | 11 | Installation 12 | ------------ 13 | 14 | Package can be installed with go command: 15 | 16 | go get github.com/vinzmay/go-rope 17 | 18 | Package documentation 19 | --------------------- 20 | [![GoDoc](https://godoc.org/github.com/vinzmay/go-rope?status.svg)](https://godoc.org/github.com/vinzmay/go-rope) 21 | 22 | Examples 23 | -------- 24 | 25 | ## Create a rope 26 | 27 | ```go 28 | //Empty rope 29 | r1 := new(rope.Rope) 30 | 31 | //Initialized rope 32 | r2 := rope.New("test rope") 33 | ``` 34 | 35 | ## Concatenate two ropes 36 | 37 | ```go 38 | r1 := rope.New("abc") 39 | r2 := rope.New("def") 40 | 41 | r3 := r1.Concat(r2) // "abcdef" 42 | ``` 43 | 44 | ## Split a rope 45 | 46 | ```go 47 | r1 := rope.New("abcdef") 48 | 49 | r2, r3 := r1.Split(4) // "abcd", "ef" 50 | ``` 51 | 52 | ## Delete from a rope 53 | 54 | ```go 55 | r1 := rope.New("abcdef") 56 | 57 | r2 := r1.Delete(3, 2) // "abef" 58 | ``` 59 | 60 | ## Insert in a rope 61 | 62 | ```go 63 | r1 := rope.New("abcdef") 64 | 65 | r2 := r1.Insert(3, "xxx") // "abcxxxdef" 66 | ``` 67 | 68 | ## Rope substring 69 | 70 | ```go 71 | r1 := rope.New("abcdef") 72 | 73 | r2 := r1.Substr(3, 2) // "cd" 74 | ``` 75 | -------------------------------------------------------------------------------- /rope.go: -------------------------------------------------------------------------------- 1 | package rope 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "unicode/utf8" 8 | ) 9 | 10 | //Rope represents a persistent rope data structure. 11 | type Rope struct { 12 | value []rune 13 | weight int 14 | length int 15 | left *Rope 16 | right *Rope 17 | } 18 | 19 | //isLeaf returns true if the rope is a leaf. 20 | func (rope *Rope) isLeaf() bool { 21 | return rope.left == nil 22 | } 23 | 24 | //panics if rope is nil 25 | func (rope *Rope) panicIfNil() { 26 | if rope == nil { 27 | panic(fmt.Sprintf("Operation not permitted on empty rope")) 28 | } 29 | } 30 | 31 | //New returns a new rope initialized with given string. 32 | func New(bootstrap string) *Rope { 33 | len := utf8.RuneCountInString(bootstrap) 34 | return &Rope{ 35 | value: []rune(bootstrap), 36 | weight: len, 37 | length: len} 38 | } 39 | 40 | //Len returns the length of the rope. 41 | func (rope *Rope) Len() int { 42 | if rope == nil { 43 | return 0 44 | } 45 | return rope.length 46 | } 47 | 48 | //String returns the complete string stored in the rope. 49 | func (rope *Rope) String() string { 50 | return rope.Report(1, rope.length) 51 | } 52 | 53 | //Internal struct for generating JSON 54 | type ropeForJSON struct { 55 | Value string 56 | Weight int 57 | Length int 58 | Left *ropeForJSON 59 | Right *ropeForJSON 60 | } 61 | 62 | //Utility function that transforms a *Rope in a *ropeForJSON. 63 | func (rope *Rope) toRopeForJSON() *ropeForJSON { 64 | if rope == nil { 65 | return nil 66 | } 67 | return &ropeForJSON{ 68 | Weight: rope.weight, 69 | Value: string(rope.value), 70 | Length: rope.length, 71 | Left: rope.left.toRopeForJSON(), 72 | Right: rope.right.toRopeForJSON(), 73 | } 74 | } 75 | 76 | //ToJSON converts a rope to indented JSON. 77 | func (rope *Rope) ToJSON() string { 78 | rope2 := rope.toRopeForJSON() 79 | var out bytes.Buffer 80 | b, _ := json.Marshal(rope2) 81 | json.Indent(&out, b, "", " ") 82 | return string(out.Bytes()) 83 | } 84 | 85 | //Index retrieves the rune at index. 86 | func (rope *Rope) Index(idx int) rune { 87 | if idx < 1 || idx > rope.length { 88 | panic(fmt.Sprintf("Rope - Index out of bounds %d/%d", idx, rope.length)) 89 | } 90 | 91 | if rope.isLeaf() { 92 | return rope.value[idx-1] 93 | } else if idx > rope.weight { 94 | return rope.right.Index(idx - rope.weight) 95 | } else { 96 | return rope.left.Index(idx) 97 | } 98 | } 99 | 100 | //Concat merges two ropes. 101 | func (rope *Rope) Concat(other *Rope) *Rope { 102 | //Special case: if the first rope is nil, just return the second rope 103 | if rope == nil { 104 | return other 105 | } 106 | //Special case: if the other rope is nil, just return the first rope 107 | if other == nil { 108 | return rope 109 | } 110 | //Return a new rope with 'rope' and 'other' assigned respectively 111 | //to left and right subropes. 112 | return &Rope{ 113 | weight: rope.Len(), 114 | length: rope.Len() + other.Len(), 115 | left: rope, 116 | right: other, 117 | } 118 | } 119 | 120 | //Internal function used by Split function. 121 | func (rope *Rope) split(idx int, secondRope *Rope) (*Rope, *Rope) { 122 | //If idx is equal to rope weight, we're arrived: 123 | //- If is leaf, return it; 124 | //- Otherwise, return its left rope. 125 | //Right rope initialises secondRope. 126 | if idx == rope.weight { 127 | var r *Rope 128 | if rope.isLeaf() { 129 | r = rope 130 | } else { 131 | r = rope.left 132 | } 133 | return r, rope.right 134 | } else if idx > rope.weight { 135 | //We have to recurse on right side. 136 | newRight, secondRope := rope.right.split(idx-rope.weight, secondRope) 137 | return rope.left.Concat(newRight), secondRope 138 | } else { 139 | //idx < rope.weight, we recurse on the left side 140 | if rope.isLeaf() { 141 | //It's a leaf: we have to create a new rope by splitting leaf at index 142 | var lr *Rope 143 | if idx > 0 { 144 | lr = &Rope{ 145 | weight: idx, 146 | value: rope.value[0:idx], 147 | length: idx, 148 | } 149 | } 150 | secondRope = &Rope{ 151 | weight: len(rope.value) - idx, 152 | value: rope.value[idx:len(rope.value)], 153 | length: len(rope.value) - idx, 154 | } 155 | return lr, secondRope 156 | } else { 157 | newLeft, secondRope := rope.left.split(idx, secondRope) 158 | return newLeft, secondRope.Concat(rope.right) 159 | } 160 | } 161 | } 162 | 163 | //Split generates two strings starting from one, splitting it at index. 164 | func (rope *Rope) Split(idx int) (firstRope *Rope, secondRope *Rope) { 165 | rope.panicIfNil() 166 | if idx < 0 || idx > rope.length { 167 | panic(fmt.Sprintf("Rope - Split out of bounds %d/%d", idx, rope.length)) 168 | } 169 | //Create the slices for split 170 | return rope.split(idx, secondRope) 171 | } 172 | 173 | //Insert generates a new rope inserting a string into the original rope. 174 | func (rope *Rope) Insert(idx int, str string) *Rope { 175 | if rope == nil { 176 | return New(str) 177 | } 178 | //Split rope at insert point 179 | r1, r2 := rope.Split(idx) 180 | //Rejoin the two split parts with string to insert as middle rope 181 | return r1.Concat(New(str)).Concat(r2) 182 | } 183 | 184 | //Delete generates a new rope by deleting from 185 | //the original one starting at idx. 186 | func (rope *Rope) Delete(idx int, length int) *Rope { 187 | rope.panicIfNil() 188 | 189 | r1, r2 := rope.Split(idx - 1) 190 | _, r4 := r2.Split(length) 191 | return r1.Concat(r4) 192 | } 193 | 194 | //Report return a substring of the rope starting from index included. 195 | func (rope *Rope) Report(idx int, length int) string { 196 | if rope == nil { 197 | return "" 198 | } 199 | res := make([]rune, length) 200 | rope.internalReport(idx, length, res) 201 | return string(res) 202 | } 203 | 204 | func (rope *Rope) internalReport(idx int, length int, res []rune) { 205 | //if idx > rope.weight we go right with modified idx 206 | if idx > rope.weight { 207 | rope.right.internalReport(idx-rope.weight, length, res) 208 | } else 209 | //if idx <= rope.weight we check if the left branch 210 | //has enough values to fetch report, else we split 211 | if rope.weight >= idx+length-1 { 212 | //we have enough space, just go left or take the string 213 | if rope.isLeaf() { 214 | //we're in a leaf, fetch from here 215 | copy(res, rope.value[idx-1:idx+length-1]) 216 | } else { 217 | rope.left.internalReport(idx, length, res) 218 | } 219 | } else { 220 | //Split the work 221 | rope.left.internalReport(idx, rope.weight-idx+1, res[:rope.weight]) 222 | rope.right.internalReport(1, length-rope.weight+idx-1, res[rope.weight:]) 223 | } 224 | } 225 | 226 | //Substr returns part of the rope, starting at index. 227 | func (rope *Rope) Substr(idx int, length int) *Rope { 228 | if idx < 1 { 229 | rope.Report(1, length) 230 | } 231 | if idx+length-1 > rope.length { 232 | rope.Report(idx, rope.length-idx+1) 233 | } 234 | 235 | _, r1 := rope.Split(idx - 1) 236 | r2, _ := r1.Split(length) 237 | return r2 238 | } 239 | --------------------------------------------------------------------------------