(Translated by https://www.hiragana.jp/)
GitHub - chewxy/skiprope: package skiprope is a rope-like data structure built on top of skiplists
Skip to content

package skiprope is a rope-like data structure built on top of skiplists

License

Notifications You must be signed in to change notification settings

chewxy/skiprope

Repository files navigation

SkipRope GoDoc Build Status Coverage Status

package skiprope is an implementation of the rope data structure. It's not strictly a rope as per se. It's not built on top of a binary tree like most rope data structures are.

Instead it's built on top of a skip list. This makes it more akin to a piece table than a true rope.

This library is quite complete as it is (it has all the functions and I need for my projects). However, any addition, corrections etc are welcome. Please see CONTRIBUTING.md for more information on how to contribute.

Installing

This package is go-gettable: go get -u github.com/chewxy/skiprope.

This package is versioned with SemVer 2.0, and does not have any external dependencies except for testing (of which the fantastic assert package is used).

Usage

r := skiprope.New()
if err := r.Insert(0, "Hello World! Hello, 世界せかい"); err != nil {
	// handle error
}
if err  := r.Insert(5, "there "); err != nil {
	// handle error
}
fmt.Println(r.String()) // "Hello there World! Hello, 世界せかい"

FAQ

How do I keep track of row, col information?

The best way is to use a linebuffer data structure in conjunction with the Rope:

type Foo struct{
	*skiprope.Rope
	linebuf []int
}

The linebuf is essentially a list of offsets to a newline character. The main reason why I didn't add a linebuf slice into the *Rope data structure is because while it's a common thing to do, for a couple of my projects I didn't need it. The nature of Go's composable data structure and programs make it quite easy to add these things. A pro-tip is to extend the Insert, InsertRunes and EraseAt methods.

I do not think it to be wise to add it to the core data structure.

When is *Rope going to implement io.Writer and io.Reader?

The main reason why I didn't do it was mostly because I didn't need it. However, I've been asked about this before. I personally don't have the bandwidth to do it.

Please send a pull request :)

Benchmarks

There is a benchmark mini-program that is not required for the running, but here are the results:

go test -run=^$ -bench=. -benchmem -cpuprofile=test.prof
BenchmarkNaiveRandomInsert-8   	   50000	     37774 ns/op	      15 B/op	       0 allocs/op
BenchmarkRopeRandomInsert-8    	 3000000	       407 ns/op	      27 B/op	       0 allocs/op
BenchmarkERopeRandomInsert-8   	 1000000	      2143 ns/op	    1161 B/op	      25 allocs/op
PASS

Fuzz testing

Guard against crashing for certain inputs by running fuzz tests. First, build the instrumented binaries for the tests and then run them.

$ go-fuzz-build github.com/chewxy/skiprope
$ go-fuzz -bin=skiprope-fuzz.zip -workdir=workdir

History, Development and Acknowledgements

This package started its life as a textbook binary-tree data structure for another personal project of mine. Over time, I decided to add more and more optimizations to it. The original design had a split at every new-line character.

I started by moving more and more things off the heap, and onto the stack. As I wondered how to incorporate a search structure using a skiplist, I stumbled onto a well developed library for C, librope.

It had everything I had wanted: a rope-like structure, using skiplists to find nodes, minimal allocations on heap, and a solution to my problem wrt keeping the skiplists off heap. The solution turned out to be to be the skiplist data structure, without a pointer. So I ended up adapting most of Joseph Gentle's algorithm. So this library owes most of its work to Joseph Gentle.

Hours before releasing this library, I had a consult by Egon Elbre, who gave good advice on whether just sticking with []rune was a good idea. He managed to convince me that it isn't, so the first pull request was made to update this library to deal with []byte instead. As a result, memory use went down 40B/op at the cost of an increase of about 30 ns/op. The number can be further shaved down with better optimizations.