From 845c99b7983212a97ad5c01eecb5b2c568955001 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 5 Feb 2024 19:37:21 -0500 Subject: [PATCH] Faster Expandable Text calculator. --- .../ui/components/ExpandableRichTextViewer.kt | 2 +- .../ExpandableViewComputationBenchmark.kt | 27 ++++++++-- .../amethyst/commons/ExpandableTextParser.kt | 50 ++++++++++--------- 3 files changed, 52 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt index 325211aa2..fc845ba2a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt @@ -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) { diff --git a/benchmark/src/androidTest/java/com/vitorpamplona/amethyst/benchmark/ExpandableViewComputationBenchmark.kt b/benchmark/src/androidTest/java/com/vitorpamplona/amethyst/benchmark/ExpandableViewComputationBenchmark.kt index a1c6dbde9..b4e52f3d3 100644 --- a/benchmark/src/androidTest/java/com/vitorpamplona/amethyst/benchmark/ExpandableViewComputationBenchmark.kt +++ b/benchmark/src/androidTest/java/com/vitorpamplona/amethyst/benchmark/ExpandableViewComputationBenchmark.kt @@ -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), ) } } diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/ExpandableTextParser.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/ExpandableTextParser.kt index a194933a0..38b43528a 100644 --- a/commons/src/main/java/com/vitorpamplona/amethyst/commons/ExpandableTextParser.kt +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/ExpandableTextParser.kt @@ -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 +}