Faster Expandable Text calculator.

This commit is contained in:
Vitor Pamplona 2024-02-05 19:37:21 -05:00
parent b13b832696
commit 845c99b798
3 changed files with 52 additions and 27 deletions

View File

@ -64,7 +64,7 @@ fun ExpandableRichTextViewer(
) {
var showFullText by remember { mutableStateOf(false) }
val whereToCut = remember(content) { ExpandableTextParser().computeWhereToCutIfPostIsTooLong(content) }
val whereToCut = remember(content) { ExpandableTextParser.computeWhereToCutIfPostIsTooLong(content) }
val text by
remember(content) {

View File

@ -24,6 +24,7 @@ import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.amethyst.commons.ExpandableTextParser
import com.vitorpamplona.amethyst.commons.nthIndexOf
import junit.framework.TestCase
import org.junit.Rule
import org.junit.Test
@ -38,18 +39,38 @@ class ExpandableViewComputationBenchmark {
fun computeTestCase1() {
benchmarkRule.measureRepeated {
TestCase.assertEquals(
294,
ExpandableTextParser().computeWhereToCutIfPostIsTooLong(testCase1),
293,
testCase1.nthIndexOf('\n', 10),
)
}
}
@Test
fun computeTestCase2() {
benchmarkRule.measureRepeated {
TestCase.assertEquals(
423,
testCase2.nthIndexOf('\n', 10),
)
}
}
@Test
fun computeTestCase1All() {
benchmarkRule.measureRepeated {
TestCase.assertEquals(
293,
ExpandableTextParser.computeWhereToCutIfPostIsTooLong(testCase1),
)
}
}
@Test
fun computeTestCase2All() {
benchmarkRule.measureRepeated {
TestCase.assertEquals(
355,
ExpandableTextParser().computeWhereToCutIfPostIsTooLong(testCase2),
ExpandableTextParser.computeWhereToCutIfPostIsTooLong(testCase2),
)
}
}

View File

@ -22,31 +22,35 @@ package com.vitorpamplona.amethyst.commons
class ExpandableTextParser {
companion object {
const val SHORT_TEXT_LENGTH = 350
const val SHORTEN_AFTER_LINES = 10
}
private const val SHORT_TEXT_LENGTH = 350
private const val SHORTEN_AFTER_LINES = 10
fun computeWhereToCutIfPostIsTooLong(content: String): Int {
// Cuts the text in the first space or new line after SHORT_TEXT_LENGTH characters
val firstSpaceAfterCut =
content.indexOf(' ', SHORT_TEXT_LENGTH).let { if (it < 0) content.length else it }
val firstNewLineAfterCut =
content.indexOf('\n', SHORT_TEXT_LENGTH).let { if (it < 0) content.length else it }
fun computeWhereToCutIfPostIsTooLong(content: String): Int {
// Cuts the text in the first space or new line after SHORT_TEXT_LENGTH characters
val firstSpaceAfterCut =
content.indexOf(' ', SHORT_TEXT_LENGTH).let { if (it < 0) content.length else it }
val firstNewLineAfterCut =
content.indexOf('\n', SHORT_TEXT_LENGTH).let { if (it < 0) content.length else it }
val firstLineAfterLineLimits =
content.nthIndexOf('\n', SHORTEN_AFTER_LINES).let { if (it < 0) content.length else it }
// or after SHORTEN_AFTER_LINES lines
val numberOfLines = content.count { it == '\n' }
var charactersInLines = minOf(firstSpaceAfterCut, firstNewLineAfterCut)
if (numberOfLines > SHORTEN_AFTER_LINES) {
val shortContent = content.lines().take(SHORTEN_AFTER_LINES)
charactersInLines = 0
for (line in shortContent) {
// +1 because new line character is omitted from .lines
charactersInLines += (line.length + 1)
}
return minOf(firstSpaceAfterCut, firstNewLineAfterCut, firstLineAfterLineLimits)
}
return minOf(firstSpaceAfterCut, firstNewLineAfterCut, charactersInLines)
}
}
fun String.nthIndexOf(
ch: Char,
N: Int,
): Int {
var occur = N
var pos = -1
while (occur > 0) {
// calling the native function multiple times is faster than looping just once
pos = indexOf(ch, pos + 1)
occur--
}
return if (occur == 0) pos else -1
}