Skip to content

Commit

Permalink
add BigTextTransformerImpl with transformInsert, length, substring an…
Browse files Browse the repository at this point in the history
…d buildString functions tested
  • Loading branch information
sunny-chung committed Sep 8, 2024
1 parent 63e23c1 commit 0255da3
Show file tree
Hide file tree
Showing 8 changed files with 589 additions and 104 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import com.williamfiset.algorithms.datastructures.balancedtree.RedBlackTree
import java.util.SortedSet
import kotlin.random.Random

class BigTextNodeValue : Comparable<BigTextNodeValue>, DebuggableNode<BigTextNodeValue> {
open class BigTextNodeValue : Comparable<BigTextNodeValue>, DebuggableNode<BigTextNodeValue>, LengthNodeValue {
var leftNumOfLineBreaks: Int = -1
var leftNumOfRowBreaks: Int = -1
var leftStringLength: Int = -1
override var leftStringLength: Int = -1
// var rowBreakOffsets: SortedSet<Int> = sortedSetOf()
/**
* Row break positions in the domain of character indices of the {bufferIndex}-th buffer.
Expand All @@ -24,9 +24,27 @@ class BigTextNodeValue : Comparable<BigTextNodeValue>, DebuggableNode<BigTextNod
lateinit var buffer: TextBuffer
var bufferOwnership: BufferOwnership = BufferOwnership.Owned

val bufferLength: Int
override val bufferLength: Int
get() = bufferOffsetEndExclusive - bufferOffsetStart

override val leftOverallLength: Int
get() = leftStringLength

override val currentOverallLength: Int
get() = bufferLength

override val leftRenderLength: Int
get() = leftStringLength

override val currentRenderLength: Int
get() = bufferLength

open val renderBufferStart: Int
get() = bufferOffsetStart

open val renderBufferEndExclusive: Int
get() = bufferOffsetEndExclusive

internal var node: RedBlackTree<BigTextNodeValue>.Node? = null

private val key = Random.nextInt()
Expand All @@ -40,7 +58,7 @@ class BigTextNodeValue : Comparable<BigTextNodeValue>, DebuggableNode<BigTextNod
}

override fun compareTo(other: BigTextNodeValue): Int {
return compareValues(leftStringLength, other.leftStringLength)
return compareValues(leftOverallLength, other.leftOverallLength)
}

override fun debugKey(): String = "$key"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.sunnychung.application.multiplatform.hellohttp.ux.bigtext

import com.williamfiset.algorithms.datastructures.balancedtree.RedBlackTree

/**
*
* o r i g i n a l T R A N S F O R M # # # t e x t
* | | | | | | | |
* leftStringLength +*************+ | | +***+*+*****+
* leftTransformedLength | | +***************+ |---| | |
* leftRenderLength +*************+*+***************+ | | +*****+
* leftOverallLength +*************+*+***************+*+***+*+*****+
*
* # is a deleted character.
*/
class BigTextTransformNodeValue : BigTextNodeValue() {
var transformedBufferStart: Int = -1
var transformedBufferEndExclusive: Int = -1

override val renderBufferStart: Int
get() = if (bufferOwnership == BufferOwnership.Delegated) {
bufferOffsetStart
} else {
transformedBufferStart
}

override val renderBufferEndExclusive: Int
get() = if (bufferOwnership == BufferOwnership.Delegated) {
bufferOffsetEndExclusive
} else {
transformedBufferEndExclusive
}

var leftTransformedLength: Int = 0
val currentTransformedLength: Int
get() = transformedBufferEndExclusive - transformedBufferStart - bufferLength

override var leftRenderLength: Int = 0
override val currentRenderLength: Int
get() = if (bufferOwnership == BufferOwnership.Delegated) {
bufferLength
} else {
transformedBufferEndExclusive - transformedBufferStart
}

override var leftOverallLength: Int = 0
override val currentOverallLength: Int
get() = bufferLength + currentRenderLength

override fun debugLabel(node: RedBlackTree<BigTextNodeValue>.Node): String = buildString {
node as RedBlackTree<BigTextTransformNodeValue>.Node

append("$leftStringLength ${bufferOwnership.name.first()} [$bufferIndex: $bufferOffsetStart ..< $bufferOffsetEndExclusive] L ${node.renderLength()}")
append(" Tr [$transformedBufferStart ..< $transformedBufferEndExclusive]")
append(" Ren left=$leftRenderLength [$renderBufferStart ..< $renderBufferEndExclusive]")
append(" row $leftNumOfRowBreaks/$rowBreakOffsets lw $lastRowWidth")
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package com.sunnychung.application.multiplatform.hellohttp.ux.bigtext

import com.sunnychung.application.multiplatform.hellohttp.extension.length
import com.williamfiset.algorithms.datastructures.balancedtree.RedBlackTree

class BigTextTransformerImpl(private val delegate: BigTextImpl) : BigTextImpl(chunkSize = delegate.chunkSize) {

override val tree: LengthTree<BigTextNodeValue> = LengthTree<BigTextTransformNodeValue>(
object : RedBlackTreeComputations<BigTextTransformNodeValue> {
override fun recomputeFromLeaf(it: RedBlackTree<BigTextTransformNodeValue>.Node) = recomputeAggregatedValues(it as RedBlackTree<BigTextNodeValue>.Node)
override fun computeWhenLeftRotate(x: BigTextTransformNodeValue, y: BigTextTransformNodeValue) {}
override fun computeWhenRightRotate(x: BigTextTransformNodeValue, y: BigTextTransformNodeValue) {}
}
)

private fun BigTextNodeValue.toBigTextTransformNodeValue() : BigTextTransformNodeValue {
return BigTextTransformNodeValue().also {
it.leftNumOfLineBreaks = leftNumOfLineBreaks
it.leftNumOfRowBreaks = leftNumOfRowBreaks
it.leftStringLength = leftStringLength
it.rowBreakOffsets = rowBreakOffsets.toList()
it.lastRowWidth = lastRowWidth
it.isEndWithForceRowBreak = isEndWithForceRowBreak
it.bufferOffsetStart = bufferOffsetStart
it.bufferOffsetEndExclusive = bufferOffsetEndExclusive
it.bufferNumLineBreaksInRange = bufferNumLineBreaksInRange
it.buffer = buffer // copy by ref
it.bufferOwnership = BufferOwnership.Delegated
}
}

fun RedBlackTree<BigTextNodeValue>.Node.toBigTextTransformNode(parentNode: RedBlackTree<BigTextTransformNodeValue>.Node) : RedBlackTree<BigTextTransformNodeValue>.Node {
if (this === delegate.tree.NIL) {
return (tree as LengthTree<BigTextTransformNodeValue>).NIL
}

return (tree as LengthTree<BigTextTransformNodeValue>).Node(
value.toBigTextTransformNodeValue(),
color,
parentNode,
tree.NIL,
tree.NIL,
).also {
it.left = left.toBigTextTransformNode(it)
it.right = right.toBigTextTransformNode(it)
}
}

init {
(tree as LengthTree<BigTextTransformNodeValue>).setRoot(delegate.tree.getRoot().toBigTextTransformNode(tree.NIL))
layouter = delegate.layouter
contentWidth = delegate.contentWidth
}

override val length: Int
get() = (tree as LengthTree<BigTextTransformNodeValue>).getRoot().renderLength()

val originalLength: Int
get() = tree.getRoot().length()

override fun createNodeValue(): BigTextNodeValue {
return BigTextTransformNodeValue()
}

fun insertOriginal(pos: Int, nodeValue: BigTextNodeValue) { // FIXME call me
require(pos in 0 .. originalLength) { "Out of bound. pos = $pos, originalLength = $originalLength" }

insertChunkAtPosition(
position = pos,
chunkedStringLength = nodeValue.bufferLength,
ownership = BufferOwnership.Delegated,
buffer = nodeValue.buffer,
range = nodeValue.bufferOffsetStart until nodeValue.bufferOffsetEndExclusive
) {
bufferIndex = -1
bufferOffsetStart = nodeValue.bufferOffsetStart
bufferOffsetEndExclusive = nodeValue.bufferOffsetEndExclusive
this.buffer = nodeValue.buffer
this.bufferOwnership = BufferOwnership.Delegated

leftStringLength = 0
}
}

private fun transformInsertChunkAtPosition(position: Int, chunkedString: String) {
log.d { "transformInsertChunkAtPosition($position, $chunkedString)" }
require(chunkedString.length <= chunkSize)
var buffer = if (buffers.isNotEmpty()) {
buffers.last().takeIf { it.length + chunkedString.length <= chunkSize }
} else null
if (buffer == null) {
buffer = TextBuffer(chunkSize)
buffers += buffer
}
require(buffer.length + chunkedString.length <= chunkSize)
val range = buffer.append(chunkedString)
insertChunkAtPosition(position, chunkedString.length, BufferOwnership.Owned, buffer, range) {
this as BigTextTransformNodeValue
bufferIndex = -1
bufferOffsetStart = -1
bufferOffsetEndExclusive = -1
transformedBufferStart = range.start
transformedBufferEndExclusive = range.endInclusive + 1
this.buffer = buffer
this.bufferOwnership = BufferOwnership.Owned

leftStringLength = 0
}
}

fun transformInsert(pos: Int, text: String): Int {
require(pos in 0 .. originalLength) { "Out of bound. pos = $pos, originalLength = $originalLength" }

/**
* As insert position is searched by leftmost of original string position,
* the insert is done by inserting to the same point in reverse order,
* which is different from BigTextImpl#insertAt.
*/

var start = text.length
var last = buffers.lastOrNull()?.length
while (start > 0) {
if (last == null || last >= chunkSize) {
// buffers += TextBuffer()
last = 0
}
val available = chunkSize - last
val append = minOf(available, start)
start -= append
transformInsertChunkAtPosition(pos, text.substring(start until start + append))
last = buffers.last().length
}
layout(maxOf(0, pos - 1), minOf(length, pos + text.length + 1))
return text.length
}

fun deleteOriginal(originalRange: IntRange) { // FIXME call me
require((originalRange.endInclusive + 1) in 0 .. originalLength) { "Out of bound. endExclusive = ${originalRange.endInclusive + 1}, originalLength = $originalLength" }
super.delete(originalRange)
}

fun transformDelete(originalRange: IntRange): Int {
require(originalRange.start <= originalRange.endInclusive + 1) { "start should be <= endExclusive" }
require(0 <= originalRange.start) { "Invalid start" }
require(originalRange.endInclusive + 1 <= originalLength) { "endExclusive is out of bound" }

if (originalRange.start == originalRange.endInclusive + 1) {
return 0
}

val startNode = tree.findNodeByCharIndex(originalRange.start)!!
val buffer = startNode.value.buffer // the buffer is not used. just to prevent NPE
super.delete(originalRange)
insertChunkAtPosition(originalRange.start, originalRange.length, BufferOwnership.Owned, buffer, -2 .. -2) {
this as BigTextTransformNodeValue
bufferIndex = -1
bufferOffsetStart = 0
bufferOffsetEndExclusive = originalRange.length
transformedBufferStart = -2
transformedBufferEndExclusive = -2
this.buffer = buffer
this.bufferOwnership = BufferOwnership.Owned

leftStringLength = 0
}
return - originalRange.length
}

override fun computeCurrentNodeProperties(nodeValue: BigTextNodeValue, left: RedBlackTree<BigTextNodeValue>.Node?) = with (nodeValue) {
super.computeCurrentNodeProperties(nodeValue, left)

this as BigTextTransformNodeValue
left as RedBlackTree<BigTextTransformNodeValue>.Node?
leftTransformedLength = left?.transformedOffset() ?: 0
leftRenderLength = left?.renderLength() ?: 0
leftOverallLength = left?.overallLength() ?: 0
}
}

fun RedBlackTree<BigTextTransformNodeValue>.Node.transformedOffset(): Int =
(getValue()?.leftTransformedLength ?: 0) +
(getValue()?.currentTransformedLength ?: 0) +
(getRight().takeIf { it.isNotNil() }?.transformedOffset() ?: 0)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.sunnychung.application.multiplatform.hellohttp.ux.bigtext

interface LengthNodeValue {
val leftStringLength: Int

val bufferLength: Int

val leftOverallLength: Int
val currentOverallLength: Int

val leftRenderLength: Int
val currentRenderLength: Int
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.sunnychung.application.multiplatform.hellohttp.ux.bigtext

import com.williamfiset.algorithms.datastructures.balancedtree.RedBlackTree

open class LengthTree<out V>(computations: RedBlackTreeComputations<V>) : RedBlackTree2<@UnsafeVariance V>(computations)
where V : LengthNodeValue, V : Comparable<@UnsafeVariance V>, V : DebuggableNode<in @UnsafeVariance V> {

fun findNodeByCharIndex(index: Int): RedBlackTree<V>.Node? {
var find = index
return findNode {
when (find) {
in Int.MIN_VALUE until it.value.leftStringLength -> -1
it.value.leftStringLength, in it.value.leftStringLength until it.value.leftStringLength + it.value.bufferLength -> {
if (it.left.isNotNil() && find == it.value.leftStringLength && it.left.value.bufferLength == 0) {
-1
} else {
0
}
}
in it.value.leftStringLength + it.value.bufferLength until Int.MAX_VALUE -> 1.also { compareResult ->
val isTurnRight = compareResult > 0
if (isTurnRight) {
find -= it.value.leftStringLength + it.value.bufferLength
}
}
else -> throw IllegalStateException("what is find? $find")
}
}
}

fun findNodeByRenderCharIndex(index: Int): RedBlackTree<V>.Node? {
var find = index
return findNode {
when (find) {
in Int.MIN_VALUE until it.value.leftRenderLength -> -1
in it.value.leftRenderLength until it.value.leftRenderLength + it.value.currentRenderLength -> 0
in it.value.leftRenderLength + it.value.currentRenderLength until Int.MAX_VALUE -> 1.also { compareResult ->
val isTurnRight = compareResult > 0
if (isTurnRight) {
find -= it.value.leftRenderLength + it.value.currentRenderLength
}
}
else -> throw IllegalStateException("what is find? $find")
}
}
}
}

fun <V> RedBlackTree<V>.Node.length(): Int where V : LengthNodeValue, V : Comparable<V> =
(getValue()?.leftStringLength ?: 0) +
(getValue()?.bufferLength ?: 0) +
(getRight().takeIf { it.isNotNil() }?.length() ?: 0)

fun <V> RedBlackTree<V>.Node.renderLength(): Int where V : LengthNodeValue, V : Comparable<V> =
(getValue()?.leftRenderLength ?: 0) +
(getValue()?.currentRenderLength ?: 0) +
(getRight().takeIf { it.isNotNil() }?.renderLength() ?: 0)

fun <V> RedBlackTree<V>.Node.overallLength(): Int where V : LengthNodeValue, V : Comparable<V> =
(getValue()?.leftOverallLength ?: 0) +
(getValue()?.currentOverallLength ?: 0) +
(getRight().takeIf { it.isNotNil() }?.overallLength() ?: 0)
Loading

0 comments on commit 0255da3

Please sign in to comment.