Skip to content

Commit c697d10

Browse files
committed
simplify
Signed-off-by: alperozturk96 <alper_ozturk@proton.me>
1 parent 80e5ba0 commit c697d10

2 files changed

Lines changed: 71 additions & 69 deletions

File tree

app/src/main/java/com/owncloud/android/utils/sort/AlphanumericComparator.kt

Lines changed: 58 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
package com.owncloud.android.utils.sort
88

99
import com.owncloud.android.lib.resources.files.model.ServerFileInterface
10-
import java.math.BigInteger
1110
import java.text.Collator
1211

1312
/**
1413
* Sorts names the way people read them, so `abc2` comes before `abc10` instead of after it.
14+
*
1515
*/
1616
class AlphanumericComparator<T : Any> : Comparator<T> {
1717

@@ -24,67 +24,67 @@ class AlphanumericComparator<T : Any> : Comparator<T> {
2424
compare(first.fileName, second.fileName)
2525

2626
@JvmStatic
27-
fun compare(first: String, second: String): Int {
28-
if (first == second) {
29-
return 0
30-
}
31-
32-
val ourChunks = chunks(first)
33-
val theirChunks = chunks(second)
27+
fun compare(first: String, second: String): Int = if (first == second) 0 else compareByChunks(first, second)
3428

35-
val byChunk = ourChunks.asSequence()
36-
.zip(theirChunks.asSequence()) { ours, theirs -> compareChunks(ours, theirs) }
37-
.firstOrNull { it != 0 }
29+
private fun compareByChunks(first: String, second: String): Int {
30+
var ourStart = 0
31+
var theirStart = 0
3832

39-
return byChunk
40-
?: ourChunks.size.compareTo(theirChunks.size).takeIf { it != 0 }
41-
?: first.compareTo(second)
42-
}
43-
44-
private val collator = ThreadLocal.withInitial { Collator.getInstance() }
33+
while (ourStart < first.length && theirStart < second.length) {
34+
val ours = chunkAt(first, ourStart)
35+
val theirs = chunkAt(second, theirStart)
4536

46-
private fun chunks(name: String): List<String> {
47-
val chunks = mutableListOf<String>()
48-
var start = 0
37+
val byChunk = compareChunks(ours, theirs)
38+
if (byChunk != 0) {
39+
return byChunk
40+
}
4941

50-
while (start < name.length) {
51-
val end = chunkEnd(name, start)
52-
chunks += name.substring(start, end)
53-
start = end
42+
ourStart += ours.length
43+
theirStart += theirs.length
5444
}
5545

56-
return chunks
46+
return when (val byLength = first.length.compareTo(second.length)) {
47+
0 -> first.compareTo(second)
48+
else -> byLength
49+
}
5750
}
5851

59-
private fun chunkEnd(name: String, start: Int): Int {
60-
if (name[start].isSeparator()) {
61-
return start + 1
62-
}
52+
private val collators: ThreadLocal<Collator> = ThreadLocal.withInitial { Collator.getInstance() }
53+
54+
private val collator: Collator
55+
get() = collators.get() ?: Collator.getInstance()
6356

64-
val digitRun = name[start].isAsciiDigit()
57+
private fun chunkAt(name: String, start: Int): String {
58+
val kind = kindOf(name[start])
6559
var end = start + 1
66-
while (end < name.length && !name[end].isSeparator() && name[end].isAsciiDigit() == digitRun) {
67-
end++
60+
61+
if (kind != ChunkKind.SEPARATOR) {
62+
while (end < name.length && kindOf(name[end]) == kind) {
63+
end++
64+
}
6865
}
69-
return end
66+
67+
return name.substring(start, end)
7068
}
7169

7270
private fun compareChunks(ours: String, theirs: String): Int {
73-
val ourRank = rankOf(ours)
74-
val byRank = ourRank.compareTo(rankOf(theirs))
75-
76-
return when {
77-
byRank != 0 -> byRank
78-
ourRank == RANK_SEPARATOR -> compareSeparators(ours[0], theirs[0])
79-
ourRank == RANK_NUMBER -> compareNumbers(ours, theirs)
80-
else -> compareText(ours, theirs)
71+
val kind = kindOf(ours[0])
72+
val byKind = kind.compareTo(kindOf(theirs[0]))
73+
if (byKind != 0) {
74+
return byKind
75+
}
76+
77+
return when (kind) {
78+
ChunkKind.SEPARATOR -> compareSeparators(ours[0], theirs[0])
79+
ChunkKind.NUMBER -> compareNumbers(ours, theirs)
80+
ChunkKind.TEXT -> compareText(ours, theirs)
8181
}
8282
}
8383

84-
private fun rankOf(chunk: String): Int = when {
85-
chunk[0].isSeparator() -> RANK_SEPARATOR
86-
chunk[0].isAsciiDigit() -> RANK_NUMBER
87-
else -> RANK_TEXT
84+
private fun kindOf(char: Char): ChunkKind = when {
85+
char <= LAST_ASCII_PUNCTUATION && !char.isLetterOrDigit() -> ChunkKind.SEPARATOR
86+
char in ZERO..NINE -> ChunkKind.NUMBER
87+
else -> ChunkKind.TEXT
8888
}
8989

9090
private fun compareSeparators(ours: Char, theirs: Char): Int = when {
@@ -94,16 +94,23 @@ class AlphanumericComparator<T : Any> : Comparator<T> {
9494
else -> ours.compareTo(theirs)
9595
}
9696

97-
private fun compareNumbers(ours: String, theirs: String): Int =
98-
when (val byValue = BigInteger(ours).compareTo(BigInteger(theirs))) {
99-
0 -> leadingZeroes(ours).compareTo(leadingZeroes(theirs))
100-
else -> byValue
97+
private fun compareNumbers(ours: String, theirs: String): Int {
98+
val ourValue = ours.trimStart(ZERO)
99+
val theirValue = theirs.trimStart(ZERO)
100+
101+
val byDigitCount = ourValue.length.compareTo(theirValue.length)
102+
if (byDigitCount != 0) {
103+
return byDigitCount
101104
}
102105

103-
private fun leadingZeroes(digits: String): Int = digits.takeWhile { it == ZERO }.length
106+
return when (val byValue = ourValue.compareTo(theirValue)) {
107+
0 -> ours.length.compareTo(theirs.length)
108+
else -> byValue
109+
}
110+
}
104111

105112
private fun compareText(ours: String, theirs: String): Int {
106-
val byCollation = collator.get()?.compare(ours, theirs) ?: 0
113+
val byCollation = collator.compare(ours, theirs)
107114
if (byCollation != 0) {
108115
return byCollation
109116
}
@@ -114,27 +121,9 @@ class AlphanumericComparator<T : Any> : Comparator<T> {
114121
}
115122
}
116123

117-
private fun Char.isAsciiDigit(): Boolean = this in ZERO..NINE
118-
119-
private fun Char.isSeparator(): Boolean = this <= LAST_CONTROL_OR_PUNCTUATION ||
120-
this in FIRST_PUNCTUATION_AFTER_DIGITS..LAST_PUNCTUATION_BEFORE_UPPERCASE ||
121-
this in FIRST_PUNCTUATION_AFTER_UPPERCASE..LAST_PUNCTUATION_BEFORE_LOWERCASE ||
122-
this in FIRST_PUNCTUATION_AFTER_LOWERCASE..LAST_ASCII_PUNCTUATION
123-
124-
private const val RANK_SEPARATOR = 0
125-
private const val RANK_NUMBER = 1
126-
private const val RANK_TEXT = 2
127-
128124
private const val DOT = '.'
129125
private const val ZERO = '0'
130126
private const val NINE = '9'
131-
132-
private const val LAST_CONTROL_OR_PUNCTUATION = '/'
133-
private const val FIRST_PUNCTUATION_AFTER_DIGITS = ':'
134-
private const val LAST_PUNCTUATION_BEFORE_UPPERCASE = '@'
135-
private const val FIRST_PUNCTUATION_AFTER_UPPERCASE = '['
136-
private const val LAST_PUNCTUATION_BEFORE_LOWERCASE = '`'
137-
private const val FIRST_PUNCTUATION_AFTER_LOWERCASE = '{'
138127
private const val LAST_ASCII_PUNCTUATION = '~'
139128
}
140129
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
package com.owncloud.android.utils.sort
8+
9+
internal enum class ChunkKind {
10+
SEPARATOR,
11+
NUMBER,
12+
TEXT
13+
}

0 commit comments

Comments
 (0)