Skip to content

Commit e212c2d

Browse files
committed
refactor(api): make Fuzzy an object
1 parent 262adac commit e212c2d

File tree

2 files changed

+169
-171
lines changed

2 files changed

+169
-171
lines changed

api/sublime-fuzzy.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
public final class com/github/androidpasswordstore/sublimefuzzy/Fuzzy {
2+
public static final field INSTANCE Lcom/github/androidpasswordstore/sublimefuzzy/Fuzzy;
23
public static final fun fuzzyMatch (Ljava/lang/String;Ljava/lang/String;)Lkotlin/Pair;
34
public static final fun fuzzyMatchSimple (Ljava/lang/String;Ljava/lang/String;)Z
45
}

src/commonMain/kotlin/com/github/androidpasswordstore/sublimefuzzy/Fuzzy.kt

Lines changed: 168 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -2,201 +2,198 @@ package com.github.androidpasswordstore.sublimefuzzy
22

33
import kotlin.jvm.JvmStatic
44

5-
public class Fuzzy private constructor() {
6-
7-
internal companion object {
8-
/**
9-
* Returns true if each character in pattern is found sequentially within str
10-
*
11-
* @param pattern the pattern to match
12-
* @param str the string to search
13-
* @return A boolean representing the match status
14-
*/
15-
@JvmStatic
16-
fun fuzzyMatchSimple(pattern: String, str: String): Boolean {
17-
var patternIdx = 0
18-
var strIdx = 0
19-
20-
val patternLength = pattern.length
21-
val strLength = str.length
22-
23-
while (patternIdx != patternLength && strIdx != strLength) {
24-
val patternChar = pattern.toCharArray()[patternIdx].lowercaseChar()
25-
val strChar = str.toCharArray()[strIdx].lowercaseChar()
26-
if (patternChar == strChar) ++patternIdx
27-
++strIdx
28-
}
5+
public object Fuzzy {
6+
/**
7+
* Returns true if each character in pattern is found sequentially within str
8+
*
9+
* @param pattern the pattern to match
10+
* @param str the string to search
11+
* @return A boolean representing the match status
12+
*/
13+
@JvmStatic
14+
public fun fuzzyMatchSimple(pattern: String, str: String): Boolean {
15+
var patternIdx = 0
16+
var strIdx = 0
17+
18+
val patternLength = pattern.length
19+
val strLength = str.length
20+
21+
while (patternIdx != patternLength && strIdx != strLength) {
22+
val patternChar = pattern.toCharArray()[patternIdx].lowercaseChar()
23+
val strChar = str.toCharArray()[strIdx].lowercaseChar()
24+
if (patternChar == strChar) ++patternIdx
25+
++strIdx
26+
}
27+
28+
return patternLength != 0 && strLength != 0 && patternIdx == patternLength
29+
}
2930

30-
return patternLength != 0 && strLength != 0 && patternIdx == patternLength
31+
private fun fuzzyMatchRecursive(
32+
pattern: String,
33+
str: String,
34+
patternCurIndexOut: Int,
35+
strCurIndexOut: Int,
36+
srcMatches: MutableList<Int>,
37+
matches: MutableList<Int>,
38+
maxMatches: Int,
39+
nextMatchOut: Int,
40+
recursionCountOut: Int,
41+
recursionLimit: Int
42+
): Pair<Boolean, Int> {
43+
var outScore = 0
44+
var strCurIndex = strCurIndexOut
45+
var patternCurIndex = patternCurIndexOut
46+
var nextMatch = nextMatchOut
47+
var recursionCount = recursionCountOut
48+
49+
// return if recursion limit is reached
50+
if (++recursionCount >= recursionLimit) {
51+
return Pair(false, outScore)
3152
}
3253

33-
private fun fuzzyMatchRecursive(
34-
pattern: String,
35-
str: String,
36-
patternCurIndexOut: Int,
37-
strCurIndexOut: Int,
38-
srcMatches: MutableList<Int>,
39-
matches: MutableList<Int>,
40-
maxMatches: Int,
41-
nextMatchOut: Int,
42-
recursionCountOut: Int,
43-
recursionLimit: Int
44-
): Pair<Boolean, Int> {
45-
var outScore = 0
46-
var strCurIndex = strCurIndexOut
47-
var patternCurIndex = patternCurIndexOut
48-
var nextMatch = nextMatchOut
49-
var recursionCount = recursionCountOut
50-
51-
// return if recursion limit is reached
52-
if (++recursionCount >= recursionLimit) {
53-
return Pair(false, outScore)
54-
}
54+
// return if we reached end of strings
55+
if (patternCurIndex == pattern.length || strCurIndex == str.length) {
56+
return Pair(false, outScore)
57+
}
5558

56-
// return if we reached end of strings
57-
if (patternCurIndex == pattern.length || strCurIndex == str.length) {
58-
return Pair(false, outScore)
59-
}
59+
// recursion params
60+
var recursiveMatch = false
61+
val bestRecursiveMatches = mutableListOf<Int>()
62+
var bestRecursiveScore = 0
63+
64+
// loop through pattern and str looking for a match
65+
var firstMatch = true
66+
while (patternCurIndex < pattern.length && strCurIndex < str.length) {
67+
// match found
68+
if (pattern[patternCurIndex].equals(str[strCurIndex], ignoreCase = true)) {
69+
if (nextMatch >= maxMatches) {
70+
return Pair(false, outScore)
71+
}
6072

61-
// recursion params
62-
var recursiveMatch = false
63-
val bestRecursiveMatches = mutableListOf<Int>()
64-
var bestRecursiveScore = 0
65-
66-
// loop through pattern and str looking for a match
67-
var firstMatch = true
68-
while (patternCurIndex < pattern.length && strCurIndex < str.length) {
69-
// match found
70-
if (pattern[patternCurIndex].equals(str[strCurIndex], ignoreCase = true)) {
71-
if (nextMatch >= maxMatches) {
72-
return Pair(false, outScore)
73-
}
73+
if (firstMatch && srcMatches.isNotEmpty()) {
74+
matches.clear()
75+
matches.addAll(srcMatches)
76+
firstMatch = false
77+
}
7478

75-
if (firstMatch && srcMatches.isNotEmpty()) {
76-
matches.clear()
77-
matches.addAll(srcMatches)
78-
firstMatch = false
79-
}
79+
val recursiveMatches = mutableListOf<Int>()
80+
val (matched, recursiveScore) =
81+
fuzzyMatchRecursive(
82+
pattern,
83+
str,
84+
patternCurIndex,
85+
strCurIndex + 1,
86+
matches,
87+
recursiveMatches,
88+
maxMatches,
89+
nextMatch,
90+
recursionCount,
91+
recursionLimit
92+
)
8093

81-
val recursiveMatches = mutableListOf<Int>()
82-
val (matched, recursiveScore) =
83-
fuzzyMatchRecursive(
84-
pattern,
85-
str,
86-
patternCurIndex,
87-
strCurIndex + 1,
88-
matches,
89-
recursiveMatches,
90-
maxMatches,
91-
nextMatch,
92-
recursionCount,
93-
recursionLimit
94-
)
95-
96-
if (matched) {
97-
// pick best recursive score
98-
if (!recursiveMatch || recursiveScore > bestRecursiveScore) {
99-
bestRecursiveMatches.clear()
100-
bestRecursiveMatches.addAll(recursiveMatches)
101-
bestRecursiveScore = recursiveScore
102-
}
103-
recursiveMatch = true
94+
if (matched) {
95+
// pick best recursive score
96+
if (!recursiveMatch || recursiveScore > bestRecursiveScore) {
97+
bestRecursiveMatches.clear()
98+
bestRecursiveMatches.addAll(recursiveMatches)
99+
bestRecursiveScore = recursiveScore
104100
}
105-
106-
matches.add(nextMatch++, strCurIndex)
107-
++patternCurIndex
101+
recursiveMatch = true
108102
}
109-
++strCurIndex
103+
104+
matches.add(nextMatch++, strCurIndex)
105+
++patternCurIndex
110106
}
107+
++strCurIndex
108+
}
111109

112-
val matched = patternCurIndex == pattern.length
110+
val matched = patternCurIndex == pattern.length
113111

114-
if (matched) {
115-
outScore = 100
112+
if (matched) {
113+
outScore = 100
116114

117-
// apply leading letter penalty
118-
val penalty =
119-
(Constants.LEADING_LETTER_PENALTY * matches[0]).coerceAtLeast(
120-
Constants.MAX_LEADING_LETTER_PENALTY
121-
)
122-
outScore += penalty
115+
// apply leading letter penalty
116+
val penalty =
117+
(Constants.LEADING_LETTER_PENALTY * matches[0]).coerceAtLeast(
118+
Constants.MAX_LEADING_LETTER_PENALTY
119+
)
120+
outScore += penalty
123121

124-
// apply unmatched penalty
125-
val unmatched = str.length - nextMatch
126-
outScore += Constants.UNMATCHED_LETTER_PENALTY * unmatched
122+
// apply unmatched penalty
123+
val unmatched = str.length - nextMatch
124+
outScore += Constants.UNMATCHED_LETTER_PENALTY * unmatched
127125

128-
// apply ordering bonuses
129-
for (i in 0 until nextMatch) {
130-
val currIdx = matches[i]
126+
// apply ordering bonuses
127+
for (i in 0 until nextMatch) {
128+
val currIdx = matches[i]
131129

132-
if (i > 0) {
133-
val prevIdx = matches[i - 1]
134-
if (currIdx == prevIdx + 1) {
135-
outScore += Constants.SEQUENTIAL_BONUS
136-
}
137-
}
138-
139-
// check for bonuses based on neighbour character value
140-
if (currIdx > 0) {
141-
// camelcase
142-
val neighbour = str[currIdx - 1]
143-
val curr = str[currIdx]
144-
if (neighbour != neighbour.uppercaseChar() && curr != curr.lowercaseChar()) {
145-
outScore += Constants.CAMEL_BONUS
146-
}
147-
val isNeighbourSeparator = neighbour == '_' || neighbour == ' '
148-
if (isNeighbourSeparator) {
149-
outScore += Constants.SEPARATOR_BONUS
150-
}
151-
} else {
152-
// first letter
153-
outScore += Constants.FIRST_LETTER_BONUS
130+
if (i > 0) {
131+
val prevIdx = matches[i - 1]
132+
if (currIdx == prevIdx + 1) {
133+
outScore += Constants.SEQUENTIAL_BONUS
154134
}
155135
}
156136

157-
// return best result
158-
return if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) {
159-
// recursive score is better than "this"
160-
matches.clear()
161-
matches.addAll(bestRecursiveMatches)
162-
outScore = bestRecursiveScore
163-
Pair(true, outScore)
164-
} else if (matched) {
165-
// "this" score is better than recursive
166-
Pair(true, outScore)
137+
// check for bonuses based on neighbour character value
138+
if (currIdx > 0) {
139+
// camelcase
140+
val neighbour = str[currIdx - 1]
141+
val curr = str[currIdx]
142+
if (neighbour != neighbour.uppercaseChar() && curr != curr.lowercaseChar()) {
143+
outScore += Constants.CAMEL_BONUS
144+
}
145+
val isNeighbourSeparator = neighbour == '_' || neighbour == ' '
146+
if (isNeighbourSeparator) {
147+
outScore += Constants.SEPARATOR_BONUS
148+
}
167149
} else {
168-
Pair(false, outScore)
150+
// first letter
151+
outScore += Constants.FIRST_LETTER_BONUS
169152
}
170153
}
171154

172-
return Pair(false, outScore)
155+
// return best result
156+
return if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) {
157+
// recursive score is better than "this"
158+
matches.clear()
159+
matches.addAll(bestRecursiveMatches)
160+
outScore = bestRecursiveScore
161+
Pair(true, outScore)
162+
} else if (matched) {
163+
// "this" score is better than recursive
164+
Pair(true, outScore)
165+
} else {
166+
Pair(false, outScore)
167+
}
173168
}
174169

175-
/**
176-
* Performs a fuzzy search to find pattern inside a string
177-
* @param pattern the the pattern to match
178-
* @param str the string to search
179-
* @return a [Pair] containing the match status as a [Boolean] and match score as an [Int]
180-
*/
181-
@JvmStatic
182-
fun fuzzyMatch(pattern: String, str: String): Pair<Boolean, Int> {
183-
val recursionCount = 0
184-
val recursionLimit = 10
185-
val matches = mutableListOf<Int>()
186-
val maxMatches = 256
187-
188-
return fuzzyMatchRecursive(
189-
pattern,
190-
str,
191-
0,
192-
0,
193-
mutableListOf(),
194-
matches,
195-
maxMatches,
196-
0,
197-
recursionCount,
198-
recursionLimit
199-
)
200-
}
170+
return Pair(false, outScore)
171+
}
172+
173+
/**
174+
* Performs a fuzzy search to find pattern inside a string
175+
* @param pattern the the pattern to match
176+
* @param str the string to search
177+
* @return a [Pair] containing the match status as a [Boolean] and match score as an [Int]
178+
*/
179+
@JvmStatic
180+
public fun fuzzyMatch(pattern: String, str: String): Pair<Boolean, Int> {
181+
val recursionCount = 0
182+
val recursionLimit = 10
183+
val matches = mutableListOf<Int>()
184+
val maxMatches = 256
185+
186+
return fuzzyMatchRecursive(
187+
pattern,
188+
str,
189+
0,
190+
0,
191+
mutableListOf(),
192+
matches,
193+
maxMatches,
194+
0,
195+
recursionCount,
196+
recursionLimit
197+
)
201198
}
202199
}

0 commit comments

Comments
 (0)