Generic run-length encoded sequence with O(log n) position lookup
Dependencies
///|
test {
// Create from a string
let rle = @rle.Rle::from_string("hello world")
// Length and lookup
inspect(rle.span(), content="11")
match rle.find(6) {
Some(pos) => {
inspect(pos.run, content="0")
inspect(pos.offset, content="6")
}
None => fail("find should succeed")
}
}///|
test {
let rle : @rle.Rle[String] = @rle.Rle::Rle()
let _ = rle.append("hello")
let _ = rle.append(" world")
inspect(rle.length(), content="1")
inspect(rle.to_string(), content="hello world")
}///|
test {
let rle = @rle.Rle::from_string("hello world")
let (left, right) = rle.split(5).unwrap()
inspect(left.to_string(), content="hello")
inspect(right.to_string(), content=" world")
}///|
test {
let rle = @rle.Rle::from_string("hello world")
let slices = rle.range(start=1, end=4).unwrap().collect()
inspect(slices.length(), content="1")
let s = slices[0]
match @rle.Sliceable::slice(s.value, start=s.start, end=s.end) {
Ok(value) => inspect(value, content="ell")
Err(_) => fail("slice should succeed")
}
}///|
test {
let rle = @rle.Rle::from_array(["a", "", "b", "", "c"])
inspect(rle.length(), content="1")
inspect(rle.to_string(), content="abc")
}///|
test {
// Insert at position
let rle = @rle.Rle::from_string("helo")
let elem = @rle.Rle::from_string("l")
let result = rle.insert(2, elem).unwrap()
inspect(result.to_string(), content="hello")
}///|
test {
// Delete a range
let rle = @rle.Rle::from_string("hello world")
let result = rle.delete(start=5, end=6).unwrap()
inspect(result.to_string(), content="helloworld")
}///|
test {
// Splice: replace a range with new content
let rle = @rle.Rle::from_string("hello world")
let replacement = @rle.Rle::from_string("beautiful ")
let result = rle.splice(start=6, end=11, replacement).unwrap()
inspect(result.to_string(), content="hello beautiful ")
}///|
test {
let rle = @rle.Rle::from_string("abcdef")
let cursor = rle.cursor()
inspect(cursor.advance(3), content="true")
match cursor.position() {
Some(position) => inspect(position, content="3")
None => fail("cursor position should be available")
}
match cursor.current_item() {
Some(item) => inspect(item, content="abcdef")
None => fail("cursor item should be available")
}
// seek() uses binary search โ O(log n)
inspect(cursor.seek(1), content="true")
match cursor.position() {
Some(position) => inspect(position, content="1")
None => fail("cursor position should be available")
}
}///|
test {
let rle = @rle.Rle::from_string("abcdef")
let cursor = rle.cursor()
let _ = cursor.advance(3)
let _ = rle.append("ghi")
inspect(cursor.is_stale(), content="true")
inspect(cursor.next() is None, content="true")
}///|
test {
// Non-mutating concat โ returns a new Rle
let a = @rle.Rle::from_string("hello")
let b = @rle.Rle::from_string(" world")
let c = a.concat(b)
inspect(c.to_string(), content="hello world")
}///|
test {
// In-place extend โ mutates the receiver
let rle = @rle.Rle::from_string("hello")
rle.extend(@rle.Rle::from_string(" world"))
inspect(rle.to_string(), content="hello world")
}///|
test {
let rle = @rle.Rle::from_string("hello")
match rle.find(0) {
Some(pos) => {
inspect(pos.run, content="0")
inspect(pos.offset, content="0")
}
None => fail("find should succeed")
}
match rle.find(4) {
Some(pos) => {
inspect(pos.run, content="0")
inspect(pos.offset, content="4")
}
None => fail("find should succeed")
}
inspect(rle.find(5) is None, content="true")
inspect(rle.find(-1) is None, content="true")
}///|
test {
let rle = @rle.Rle::from_string("hello")
match rle.value_at(2) {
Ok(value) => inspect(value, content="hello")
Err(_) => fail("value_at should succeed")
}
inspect(rle.value_at(5) is Err(_), content="true")
}///|
test {
let rle = @rle.Rle::from_string("hello")
inspect(rle.span(), content="5")
inspect(rle.logical_length(), content="5")
inspect(rle.span() == rle.logical_length(), content="true")
}///|
test {
let rle = @rle.Rle::from_string("hello")
let _ = rle.span() // builds cache
let _ = rle.append(" world") // invalidates cache
// next query rebuilds automatically
inspect(rle.span(), content="11")
}///|
test {
let rle : @rle.Rle[String] = @rle.Rle::Rle()
inspect(rle.get_version(), content="0")
let _ = rle.append("a")
inspect(rle.get_version(), content="1")
let _ = rle.append("b")
inspect(rle.get_version(), content="2")
rle.clear()
inspect(rle.get_version(), content="3")
}///|
test {
let rle = @rle.Rle::from_string("hello")
match rle.split(100) {
Ok(_) => fail("should fail")
Err(e) =>
inspect(
e.message(),
content="Position 100 is outside the document (length: 5)",
)
}
}///|
test {
let rle = @rle.Rle::from_string("hello")
match rle.range(start=-1, end=3) {
Ok(_) => fail("should fail")
Err(e) =>
inspect(e.message(), content="Range start (-1) cannot be negative")
}
}///|
test {
let rle = @rle.Rle::from_string("hello")
match rle.range(start=0, end=10) {
Ok(_) => fail("should fail")
Err(e) =>
inspect(e.message(), content="Range end (10) exceeds document length (5)")
}
}///|
test {
let rle : @rle.Rle[String] = @rle.Rle::Rle()
inspect(rle.append("") is Err(_), content="true")
}///|
test {
let rle = @rle.Rle::from_string("A๐B")
inspect(rle.span(), content="4") // A(1) + ๐(2) + B(1)
}///|
test {
let rle = @rle.Rle::from_string("๐")
inspect(rle.span(), content="2")
match rle.split(1) {
Ok(_) => fail("should fail on invalid boundary")
Err(e) =>
inspect(e.message(), content="Slice indices are not on valid boundaries")
}
}///|
test {
let rle = @rle.Rle::from_string("A๐B")
// Position 3 is after the emoji, before B โ valid boundary
match rle.range(start=0, end=3) {
Ok(iter) => {
let slices = iter.collect()
inspect(slices.length(), content="1")
}
Err(_) => fail("range should succeed")
}
}///|
test {
let rle = @rle.Rle::from_string("ใใใซใกใฏ")
inspect(rle.span(), content="5")
match rle.find(2) {
Some(pos) => {
inspect(pos.run, content="0")
inspect(pos.offset, content="2")
}
None => fail("find should succeed")
}
match rle.split(1) {
Ok((left, right)) => {
inspect(left.to_string(), content="ใ")
inspect(right.to_string(), content="ใใซใกใฏ")
}
Err(_) => fail("split should work with unicode")
}
}///|
test {
let rle = @rle.Rle::from_string("hello")
match rle.range(start=2, end=2) {
Ok(iter) => inspect(iter.collect().length(), content="0")
Err(_) => fail("empty range should succeed")
}
}///|
test {
let rle = @rle.Rle::from_string("hello")
match rle.delete(start=2, end=2) {
Ok(result) => inspect(result.to_string(), content="hello")
Err(_) => fail("delete empty range should succeed")
}
}///|
test {
let rle = @rle.Rle::from_string("hello")
// Split at start
let (left, right) = rle.split(0).unwrap()
inspect(left.to_string(), content="")
inspect(right.to_string(), content="hello")
}///|
test {
let rle = @rle.Rle::from_string("hello")
// Split at end
let (left, right) = rle.split(5).unwrap()
inspect(left.to_string(), content="hello")
inspect(right.to_string(), content="")
}///|
test {
let rle = @rle.Rle::from_string("hello")
let slices = rle.range_clamped(start=-5, end=100).collect()
inspect(slices.length(), content="1")
let s = slices[0]
match @rle.Sliceable::slice(s.value, start=s.start, end=s.end) {
Ok(value) => inspect(value, content="hello")
Err(_) => fail("slice should succeed")
}
}///|
test {
// Create from a string
let rle = @rle.Rle::from_string("hello world")
// Length and lookup
inspect(rle.span(), content="11")
match rle.find(6) {
Some(pos) => {
inspect(pos.run, content="0")
inspect(pos.offset, content="6")
}
None => fail("find should succeed")
}
}///|
test {
let rle : @rle.Rle[String] = @rle.Rle::Rle()
let _ = rle.append("hello")
let _ = rle.append(" world")
inspect(rle.length(), content="1")
inspect(rle.to_string(), content="hello world")
}///|
test {
let rle = @rle.Rle::from_string("hello world")
let (left, right) = rle.split(5).unwrap()
inspect(left.to_string(), content="hello")
inspect(right.to_string(), content=" world")
}///|
test {
let rle = @rle.Rle::from_string("hello world")
let slices = rle.range(start=1, end=4).unwrap().collect()
inspect(slices.length(), content="1")
let s = slices[0]
match @rle.Sliceable::slice(s.value, start=s.start, end=s.end) {
Ok(value) => inspect(value, content="ell")
Err(_) => fail("slice should succeed")
}
}///|
test {
let rle = @rle.Rle::from_array(["a", "", "b", "", "c"])
inspect(rle.length(), content="1")
inspect(rle.to_string(), content="abc")
}///|
test {
// Insert at position
let rle = @rle.Rle::from_string("helo")
let elem = @rle.Rle::from_string("l")
let result = rle.insert(2, elem).unwrap()
inspect(result.to_string(), content="hello")
}///|
test {
// Delete a range
let rle = @rle.Rle::from_string("hello world")
let result = rle.delete(start=5, end=6).unwrap()
inspect(result.to_string(), content="helloworld")
}///|
test {
// Splice: replace a range with new content
let rle = @rle.Rle::from_string("hello world")
let replacement = @rle.Rle::from_string("beautiful ")
let result = rle.splice(start=6, end=11, replacement).unwrap()
inspect(result.to_string(), content="hello beautiful ")
}///|
test {
let rle = @rle.Rle::from_string("abcdef")
let cursor = rle.cursor()
inspect(cursor.advance(3), content="true")
match cursor.position() {
Some(position) => inspect(position, content="3")
None => fail("cursor position should be available")
}
match cursor.current_item() {
Some(item) => inspect(item, content="abcdef")
None => fail("cursor item should be available")
}
// seek() uses binary search โ O(log n)
inspect(cursor.seek(1), content="true")
match cursor.position() {
Some(position) => inspect(position, content="1")
None => fail("cursor position should be available")
}
}///|
test {
let rle = @rle.Rle::from_string("abcdef")
let cursor = rle.cursor()
let _ = cursor.advance(3)
let _ = rle.append("ghi")
inspect(cursor.is_stale(), content="true")
inspect(cursor.next() is None, content="true")
}///|
test {
// Non-mutating concat โ returns a new Rle
let a = @rle.Rle::from_string("hello")
let b = @rle.Rle::from_string(" world")
let c = a.concat(b)
inspect(c.to_string(), content="hello world")
}///|
test {
// In-place extend โ mutates the receiver
let rle = @rle.Rle::from_string("hello")
rle.extend(@rle.Rle::from_string(" world"))
inspect(rle.to_string(), content="hello world")
}///|
test {
let rle = @rle.Rle::from_string("hello")
match rle.find(0) {
Some(pos) => {
inspect(pos.run, content="0")
inspect(pos.offset, content="0")
}
None => fail("find should succeed")
}
match rle.find(4) {
Some(pos) => {
inspect(pos.run, content="0")
inspect(pos.offset, content="4")
}
None => fail("find should succeed")
}
inspect(rle.find(5) is None, content="true")
inspect(rle.find(-1) is None, content="true")
}///|
test {
let rle = @rle.Rle::from_string("hello")
match rle.value_at(2) {
Ok(value) => inspect(value, content="hello")
Err(_) => fail("value_at should succeed")
}
inspect(rle.value_at(5) is Err(_), content="true")
}///|
test {
let rle = @rle.Rle::from_string("hello")
inspect(rle.span(), content="5")
inspect(rle.logical_length(), content="5")
inspect(rle.span() == rle.logical_length(), content="true")
}///|
test {
let rle = @rle.Rle::from_string("hello")
let _ = rle.span() // builds cache
let _ = rle.append(" world") // invalidates cache
// next query rebuilds automatically
inspect(rle.span(), content="11")
}///|
test {
let rle : @rle.Rle[String] = @rle.Rle::Rle()
inspect(rle.get_version(), content="0")
let _ = rle.append("a")
inspect(rle.get_version(), content="1")
let _ = rle.append("b")
inspect(rle.get_version(), content="2")
rle.clear()
inspect(rle.get_version(), content="3")
}///|
test {
let rle = @rle.Rle::from_string("hello")
match rle.split(100) {
Ok(_) => fail("should fail")
Err(e) =>
inspect(
e.message(),
content="Position 100 is outside the document (length: 5)",
)
}
}///|
test {
let rle = @rle.Rle::from_string("hello")
match rle.range(start=-1, end=3) {
Ok(_) => fail("should fail")
Err(e) =>
inspect(e.message(), content="Range start (-1) cannot be negative")
}
}///|
test {
let rle = @rle.Rle::from_string("hello")
match rle.range(start=0, end=10) {
Ok(_) => fail("should fail")
Err(e) =>
inspect(e.message(), content="Range end (10) exceeds document length (5)")
}
}///|
test {
let rle : @rle.Rle[String] = @rle.Rle::Rle()
inspect(rle.append("") is Err(_), content="true")
}///|
test {
let rle = @rle.Rle::from_string("A๐B")
inspect(rle.span(), content="4") // A(1) + ๐(2) + B(1)
}///|
test {
let rle = @rle.Rle::from_string("๐")
inspect(rle.span(), content="2")
match rle.split(1) {
Ok(_) => fail("should fail on invalid boundary")
Err(e) =>
inspect(e.message(), content="Slice indices are not on valid boundaries")
}
}///|
test {
let rle = @rle.Rle::from_string("A๐B")
// Position 3 is after the emoji, before B โ valid boundary
match rle.range(start=0, end=3) {
Ok(iter) => {
let slices = iter.collect()
inspect(slices.length(), content="1")
}
Err(_) => fail("range should succeed")
}
}///|
test {
let rle = @rle.Rle::from_string("ใใใซใกใฏ")
inspect(rle.span(), content="5")
match rle.find(2) {
Some(pos) => {
inspect(pos.run, content="0")
inspect(pos.offset, content="2")
}
None => fail("find should succeed")
}
match rle.split(1) {
Ok((left, right)) => {
inspect(left.to_string(), content="ใ")
inspect(right.to_string(), content="ใใซใกใฏ")
}
Err(_) => fail("split should work with unicode")
}
}///|
test {
let rle = @rle.Rle::from_string("hello")
match rle.range(start=2, end=2) {
Ok(iter) => inspect(iter.collect().length(), content="0")
Err(_) => fail("empty range should succeed")
}
}///|
test {
let rle = @rle.Rle::from_string("hello")
match rle.delete(start=2, end=2) {
Ok(result) => inspect(result.to_string(), content="hello")
Err(_) => fail("delete empty range should succeed")
}
}///|
test {
let rle = @rle.Rle::from_string("hello")
// Split at start
let (left, right) = rle.split(0).unwrap()
inspect(left.to_string(), content="")
inspect(right.to_string(), content="hello")
}///|
test {
let rle = @rle.Rle::from_string("hello")
// Split at end
let (left, right) = rle.split(5).unwrap()
inspect(left.to_string(), content="hello")
inspect(right.to_string(), content="")
}///|
test {
let rle = @rle.Rle::from_string("hello")
let slices = rle.range_clamped(start=-5, end=100).collect()
inspect(slices.length(), content="1")
let s = slices[0]
match @rle.Sliceable::slice(s.value, start=s.start, end=s.end) {
Ok(value) => inspect(value, content="hello")
Err(_) => fail("slice should succeed")
}
}pub(open) trait Addressable {
fn address(Self, global_start : Int, offset : Int) -> Int
}pub(open) trait FromRange {
fn from_range(start : Int, count : Int) -> Self
}pub(open) trait HasLength {
fn length(Self) -> Int
fn is_empty(Self) -> Bool = _
}pub(open) trait Mergeable {
fn can_merge(Self, Self) -> Bool
fn merge(Self, Self) -> Self
}HasLength::length โโโ Spanning::span โโโ Spanning::logical_length
(base) (defaults to length) (defaults to span)impl Show for InternalErrorpub(all) suberror RleError {
PositionOutOfBounds(Int, Int)
InvalidRange(Int, Int, Int, RangeIssue)
InvalidSlice(SliceError)
Internal(InternalError)
} derive(Debug)impl Show for SliceErrorpub struct ErrorMessage {
// private fields
}impl Show for ErrorMessagepub(all) struct PrefixSums {
spans : Array[Int]
content : Array[Int]
} derive(Eq, Debug)
fn PrefixSums::PrefixSums() -> PrefixSumsimpl HasLength for PrefixSumsimpl Spanning for PrefixSumsimpl Show for PrefixSumsimpl Arbitrary for PrefixSumsimpl Shrink for PrefixSumsimpl Show for RangeIssuepub struct Rle[T] {
runs : Runs[T]
prefix : PrefixSums?
version : Int
} derive(Eq, Debug)
fn Rle::Rle() -> Rle[T]Rle: ["abc", "de"] (spans: 3, 2)
each_with_position yields:
f("abc", 0, 3) โ positions [0, 3)
f("de", 3, 5) โ positions [3, 5)Rle::from_sorted_ints([0, 1, 2, 5, 6, 7])
โ groups: [range(0, 3), range(5, 3)]
โ Rle with 2 runs, total span 6let cursor = rle.cursor() // captures version
cursor.advance(5) // move forward
cursor.current_item() // read current run
rle.append(x) // mutation! version bumps
cursor.is_stale() // true โ cursor is now invalid
cursor.next() // None โ refuses to operatefrom_sorted_ints([1, 1, 2, 3, 5, 5])
dedup โ [1, 2, 3, 5]
group โ [range(1, 3), range(5, 1)]Generic run-length encoded sequence with O(log n) position lookup
Dependencies