Optimize partition search

This commit is contained in:
Hasinjato 2026-10-08 12:31:09 +03:00
parent 334f1cb083
commit bbcb59b7e6
2 changed files with 138 additions and 38 deletions

View file

@ -17,12 +17,50 @@ data class DrawerItem(
val firstNotes: String = "",
val path: String
) {
private data class TitleComponents(
val prefix: String,
val number: String,
val version: String?
)
private fun parseComponents(str: String): TitleComponents? {
// préfixe (lettres), numéro (chiffres), version optionnelle
val regex = Regex("""^([a-zA-Z]+)[-_\s]*(\d+)(?:[-_\s]*(\d+))?""")
val match = regex.find(str.trim()) ?: return null
val prefix = match.groupValues[1].lowercase()
val number = match.groupValues[2]
val version = match.groupValues[3].ifEmpty { null }
return TitleComponents(prefix, number, version)
}
fun matchesQuery(query: String): Boolean {
if (query.isBlank()) return true
val cleanQuery = query.normalizeForSearch()
val cleanTitle = title.normalizeForSearch()
val cleanContent = contentTitle.normalizeForSearch()
return (cleanTitle + cleanContent).contains(cleanQuery)
if ((cleanTitle + cleanContent).contains(cleanQuery) || (cleanContent + cleanTitle).contains(cleanQuery)) {
return true
}
val queryComponents = parseComponents(query)
val titleComponents = parseComponents(title)
if (queryComponents != null && titleComponents != null) {
if (queryComponents.prefix == titleComponents.prefix) {
return true
}
}
val queryLetters = query.filter { it.isLetter() }.lowercase()
val queryDigits = query.filter { it.isDigit() }
if (queryLetters.isNotEmpty() && queryDigits.isNotEmpty()) {
val lettersMatch = cleanContent.contains(queryLetters) || cleanTitle.contains(queryLetters)
val digitsMatch = title.contains(queryDigits) || cleanTitle.contains(queryDigits) || cleanContent.contains(queryDigits)
return lettersMatch && digitsMatch
}
return false
}
fun getSearchScore(query: String): Int {
@ -32,20 +70,66 @@ data class DrawerItem(
val cleanTitle = title.normalizeForSearch()
val cleanContent = contentTitle.normalizeForSearch()
val queryComponents = parseComponents(query)
val titleComponents = parseComponents(title)
if (queryComponents != null && titleComponents != null) {
val samePrefix = queryComponents.prefix == titleComponents.prefix
val sameNumber = queryComponents.number == titleComponents.number
val sameVersion = queryComponents.version == titleComponents.version
if (samePrefix && sameNumber && sameVersion) {
return 3000
}
if (samePrefix && sameNumber) {
return 2500
}
if (samePrefix && !sameNumber) {
if (cleanTitle == cleanQuery) {
return 1200
}
}
}
if (title.equals(query, ignoreCase = true)) return 2000
if (cleanTitle == cleanQuery) return 1000
val titleDigitBlocks = Regex("\\d+").findAll(title).map { it.value }.toList()
val queryDigits = query.filter { it.isDigit() }
val queryLetters = query.filter { it.isLetter() }.lowercase()
if (cleanTitle.startsWith(cleanQuery)) {
val nextCharIndex = cleanQuery.length
if (queryDigits.isNotEmpty() && titleDigitBlocks.contains(queryDigits)) {
return 950
}
return 800
}
val isNextCharDigit = nextCharIndex < cleanTitle.length && cleanTitle[nextCharIndex].isDigit()
if (queryLetters.isEmpty() && queryDigits.isNotEmpty()) {
if (titleDigitBlocks.contains(queryDigits)) return 1100
if (cleanTitle.contains(queryDigits)) return 400
if (cleanContent.contains(queryDigits)) return 200
}
return if (isNextCharDigit) {
100
} else {
800
if (queryLetters.isNotEmpty() && queryDigits.isNotEmpty()) {
val lettersInContent = cleanContent.contains(queryLetters)
val lettersInTitle = cleanTitle.contains(queryLetters)
val digitsInTitleBlock = titleDigitBlocks.contains(queryDigits)
val digitsInTitle = cleanTitle.contains(queryDigits)
if ((lettersInContent || lettersInTitle) && digitsInTitleBlock) {
return 850
}
if ((lettersInContent || lettersInTitle) && digitsInTitle) {
return 500
}
}
if (cleanTitle.contains(cleanQuery)) return 50
if (cleanContent.contains(cleanQuery)) return 10
if (cleanTitle.contains(cleanQuery)) return 600
if (cleanContent.contains(cleanQuery)) return 100
return 0
}

View file

@ -9,6 +9,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
@ -1093,11 +1094,8 @@ fun MainScreenWithDrawer(
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
modifier = Modifier.fillMaxHeight()
) {
val sortedSongs = remember(filteredSongs) {
filteredSongs.sortedWith(
compareBy<DrawerItem> { item ->
!item.path.startsWith("assets://")
}.thenComparing { a, b ->
val sortedSongs = remember(filteredSongs, textInput) {
val naturalComparator = Comparator<DrawerItem> { a, b ->
val regex = Regex("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)")
val chunksA = a.title.lowercase().split(regex)
val chunksB = b.title.lowercase().split(regex)
@ -1115,15 +1113,33 @@ fun MainScreenWithDrawer(
}
if (result != 0) break
}
if (result == 0) {
chunksA.size.compareTo(chunksB.size)
if (result == 0) chunksA.size.compareTo(chunksB.size) else result
}
if (textInput.isBlank()) {
filteredSongs.sortedWith(
compareBy<DrawerItem> { item -> !item.path.startsWith("assets://") }
.thenComparing(naturalComparator)
)
} else {
result
}
}
filteredSongs.sortedWith(
compareByDescending<DrawerItem> { item -> item.getSearchScore(textInput) }
.thenBy { item -> !item.path.startsWith("assets://") }
.thenComparing(naturalComparator)
)
}
LazyColumn(Modifier.fillMaxSize()) {
}
val searchListState = rememberLazyListState()
LaunchedEffect(textInput) {
if (sortedSongs.isNotEmpty()) {
searchListState.scrollToItem(0)
}
}
LazyColumn(
state = searchListState,
modifier = Modifier.fillMaxSize()
) {
if (sortedSongs.isEmpty() && textInput.isNotEmpty()) {
item {
ListItem(