@@ -2,201 +2,198 @@ package com.github.androidpasswordstore.sublimefuzzy
22
33import 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