Optimize partition search
This commit is contained in:
parent
334f1cb083
commit
bbcb59b7e6
2 changed files with 138 additions and 38 deletions
|
|
@ -17,12 +17,50 @@ data class DrawerItem(
|
||||||
val firstNotes: String = "",
|
val firstNotes: String = "",
|
||||||
val path: 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 {
|
fun matchesQuery(query: String): Boolean {
|
||||||
if (query.isBlank()) return true
|
if (query.isBlank()) return true
|
||||||
val cleanQuery = query.normalizeForSearch()
|
val cleanQuery = query.normalizeForSearch()
|
||||||
val cleanTitle = title.normalizeForSearch()
|
val cleanTitle = title.normalizeForSearch()
|
||||||
val cleanContent = contentTitle.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 {
|
fun getSearchScore(query: String): Int {
|
||||||
|
|
@ -32,20 +70,66 @@ data class DrawerItem(
|
||||||
val cleanTitle = title.normalizeForSearch()
|
val cleanTitle = title.normalizeForSearch()
|
||||||
val cleanContent = contentTitle.normalizeForSearch()
|
val cleanContent = contentTitle.normalizeForSearch()
|
||||||
|
|
||||||
if (cleanTitle == cleanQuery) return 1000
|
val queryComponents = parseComponents(query)
|
||||||
if (cleanTitle.startsWith(cleanQuery)) {
|
val titleComponents = parseComponents(title)
|
||||||
val nextCharIndex = cleanQuery.length
|
|
||||||
|
|
||||||
val isNextCharDigit = nextCharIndex < cleanTitle.length && cleanTitle[nextCharIndex].isDigit()
|
if (queryComponents != null && titleComponents != null) {
|
||||||
|
val samePrefix = queryComponents.prefix == titleComponents.prefix
|
||||||
|
val sameNumber = queryComponents.number == titleComponents.number
|
||||||
|
val sameVersion = queryComponents.version == titleComponents.version
|
||||||
|
|
||||||
return if (isNextCharDigit) {
|
if (samePrefix && sameNumber && sameVersion) {
|
||||||
100
|
return 3000
|
||||||
} else {
|
}
|
||||||
800
|
|
||||||
|
if (samePrefix && sameNumber) {
|
||||||
|
return 2500
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samePrefix && !sameNumber) {
|
||||||
|
if (cleanTitle == cleanQuery) {
|
||||||
|
return 1200
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cleanTitle.contains(cleanQuery)) return 50
|
|
||||||
if (cleanContent.contains(cleanQuery)) return 10
|
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)) {
|
||||||
|
if (queryDigits.isNotEmpty() && titleDigitBlocks.contains(queryDigits)) {
|
||||||
|
return 950
|
||||||
|
}
|
||||||
|
return 800
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryLetters.isEmpty() && queryDigits.isNotEmpty()) {
|
||||||
|
if (titleDigitBlocks.contains(queryDigits)) return 1100
|
||||||
|
if (cleanTitle.contains(queryDigits)) return 400
|
||||||
|
if (cleanContent.contains(queryDigits)) return 200
|
||||||
|
}
|
||||||
|
|
||||||
|
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 600
|
||||||
|
if (cleanContent.contains(cleanQuery)) return 100
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
|
@ -1093,37 +1094,52 @@ fun MainScreenWithDrawer(
|
||||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
|
||||||
modifier = Modifier.fillMaxHeight()
|
modifier = Modifier.fillMaxHeight()
|
||||||
) {
|
) {
|
||||||
val sortedSongs = remember(filteredSongs) {
|
val sortedSongs = remember(filteredSongs, textInput) {
|
||||||
filteredSongs.sortedWith(
|
val naturalComparator = Comparator<DrawerItem> { a, b ->
|
||||||
compareBy<DrawerItem> { item ->
|
val regex = Regex("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)")
|
||||||
!item.path.startsWith("assets://")
|
val chunksA = a.title.lowercase().split(regex)
|
||||||
}.thenComparing { a, b ->
|
val chunksB = b.title.lowercase().split(regex)
|
||||||
val regex = Regex("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)")
|
var result = 0
|
||||||
val chunksA = a.title.lowercase().split(regex)
|
val size = minOf(chunksA.size, chunksB.size)
|
||||||
val chunksB = b.title.lowercase().split(regex)
|
for (i in 0 until size) {
|
||||||
var result = 0
|
val chunkA = chunksA[i]
|
||||||
val size = minOf(chunksA.size, chunksB.size)
|
val chunkB = chunksB[i]
|
||||||
for (i in 0 until size) {
|
val numA = chunkA.toLongOrNull()
|
||||||
val chunkA = chunksA[i]
|
val numB = chunkB.toLongOrNull()
|
||||||
val chunkB = chunksB[i]
|
result = if (numA != null && numB != null) {
|
||||||
val numA = chunkA.toLongOrNull()
|
numA.compareTo(numB)
|
||||||
val numB = chunkB.toLongOrNull()
|
|
||||||
result = if (numA != null && numB != null) {
|
|
||||||
numA.compareTo(numB)
|
|
||||||
} else {
|
|
||||||
chunkA.compareTo(chunkB)
|
|
||||||
}
|
|
||||||
if (result != 0) break
|
|
||||||
}
|
|
||||||
if (result == 0) {
|
|
||||||
chunksA.size.compareTo(chunksB.size)
|
|
||||||
} else {
|
} else {
|
||||||
result
|
chunkA.compareTo(chunkB)
|
||||||
}
|
}
|
||||||
|
if (result != 0) break
|
||||||
}
|
}
|
||||||
)
|
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 {
|
||||||
|
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()) {
|
if (sortedSongs.isEmpty() && textInput.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
ListItem(
|
ListItem(
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue