1- public protocol SourceElement {
1+ public protocol SearchElement {
22 var searchRepresentation : [ Character ] { get }
33}
44
5- public struct Match < Collection> : CustomDebugStringConvertible where Collection: RandomAccessCollection , Collection. Element: SourceElement {
5+ public struct Match < Collection> : CustomDebugStringConvertible where Collection: RandomAccessCollection , Collection. Element: SearchElement {
66 public typealias Element = Collection . Element
77 public typealias Index = Collection . Index
8- public init ( value: Element , index: Index , matchedIndices: [ Index ] , score: Int ) {
8+ public init ( value: Element , index: Index , matchedIndices: [ Int ] , score: Int ) {
99 self . value = value
1010 self . index = index
1111 self . matchedIndices = matchedIndices
1212 self . score = score
1313 }
1414 public var value : Element
1515 public var index : Index
16- public var matchedIndices : [ Index ]
16+ public var matchedIndices : [ Int ]
1717 public var score : Int
1818
1919 public var debugDescription : String {
@@ -35,14 +35,14 @@ fileprivate enum Penalty {
3535 static let maxUnmatchedLeadingChar = - 15
3636}
3737
38- extension String : SourceElement {
38+ extension String : SearchElement {
3939 @inlinable
4040 public var searchRepresentation : [ Character ] {
4141 Array ( self )
4242 }
4343}
4444
45- extension Substring : SourceElement {
45+ extension Substring : SearchElement {
4646 @inlinable
4747 public var searchRepresentation : [ Character ] {
4848 Array ( self )
@@ -54,8 +54,7 @@ public func find<Collection>(
5454 in data: Collection
5555) -> [ Match < Collection > ]
5656where Collection: RandomAccessCollection ,
57- Collection. Element: SourceElement ,
58- Collection. Index == Int
57+ Collection. Element: SearchElement
5958{
6059 var matches = findNoSort ( pattern: pattern, in: data)
6160 matches. sort { $0. score >= $1. score }
@@ -67,83 +66,98 @@ public func findNoSort<Collection>(
6766 in data: Collection
6867) -> [ Match < Collection > ]
6968where Collection: RandomAccessCollection ,
70- Collection. Element: SourceElement ,
71- Collection. Index == Int
69+ Collection. Element: SearchElement
7270{
7371 guard !pattern. isEmpty else { return [ ] }
7472
7573 let patternRunes : [ Character ] = Array ( pattern)
74+
7675 var matches = [ Match < Collection > ] ( )
77-
7876 for i in data. indices {
79- let dataRow = data [ i]
80- var matchedIndices : [ Int ] = [ ]
81- var totalScore = 0
82- var patternIndex = patternRunes. startIndex
83- var bestScore = - 1
84- var matchedIndex : Int ?
85- var currentAdjacentMatchBonus = 0
86-
87- let candidateRunes = dataRow. searchRepresentation
88- var lastIndex = candidateRunes. startIndex
89- var lastRune = Character ( " \0 " )
90-
91- for candidateIndex in candidateRunes. indices {
92- let candidateRune = candidateRunes [ candidateIndex]
93- if equalFold ( candidateRune, patternRunes [ patternIndex] ) {
94- var score = 0
95- if candidateIndex == candidateRunes. startIndex {
96- score += Bounus . firstCharMatch
97- }
98- if lastRune. isLowercase && candidateRune. isUppercase {
99- score += Bounus . camelCaseMatch
100- }
101- if candidateIndex != candidateRunes. startIndex && isSeparator ( lastRune) {
102- score += Bounus . matchFollowingSeparator
103- }
104- if let lastMatch = matchedIndices. last {
105- let bonus = adjacentCharBonus (
106- index: lastIndex,
107- lastMatch: lastMatch,
108- currentBonus: currentAdjacentMatchBonus
109- )
110- score += bonus
111- currentAdjacentMatchBonus += bonus
112- }
113- if score > bestScore {
114- bestScore = score
115- matchedIndex = candidateIndex
116- }
77+ if let match = matchTest (
78+ patternRunes: patternRunes,
79+ candidateRunes: data [ i] . searchRepresentation
80+ ) {
81+ matches. append ( . init(
82+ value: data [ i] ,
83+ index: i,
84+ matchedIndices: match. matchedIndices,
85+ score: match. score
86+ ) )
87+ }
88+ }
89+
90+ return matches
91+ }
92+
93+ fileprivate func matchTest(
94+ patternRunes: [ Character ] ,
95+ candidateRunes: [ Character ]
96+ ) -> ( matchedIndices: [ Int ] , score: Int ) ? {
97+ var matchedIndices : [ Int ] = [ ]
98+ var totalScore = 0
99+ var patternIndex = patternRunes. startIndex
100+ var bestScore = - 1
101+ var matchedIndex : Int ?
102+ var currentAdjacentMatchBonus = 0
103+
104+ var lastIndex = candidateRunes. startIndex
105+ var lastRune = Character ( " \0 " )
106+
107+ for candidateIndex in candidateRunes. indices {
108+ let candidateRune = candidateRunes [ candidateIndex]
109+ if equalFold ( candidateRune, patternRunes [ patternIndex] ) {
110+ var score = 0
111+ if candidateIndex == candidateRunes. startIndex {
112+ score += Bounus . firstCharMatch
117113 }
114+ if lastRune. isLowercase && candidateRune. isUppercase {
115+ score += Bounus . camelCaseMatch
116+ }
117+ if candidateIndex != candidateRunes. startIndex && isSeparator ( lastRune) {
118+ score += Bounus . matchFollowingSeparator
119+ }
120+ if let lastMatch = matchedIndices. last {
121+ let bonus = adjacentCharBonus (
122+ index: lastIndex,
123+ lastMatch: lastMatch,
124+ currentBonus: currentAdjacentMatchBonus
125+ )
126+ score += bonus
127+ currentAdjacentMatchBonus += bonus
128+ }
129+ if score > bestScore {
130+ bestScore = score
131+ matchedIndex = candidateIndex
132+ }
133+ }
118134
119- if let matchedIndex {
120- let nextPattern = patternRunes [ safe: patternIndex + 1 ]
121- let nextCandidate = candidateRunes [ safe: candidateIndex + 1 ]
122- if equalFold ( nextPattern, nextCandidate) || nextCandidate == nil {
123- if matchedIndices. isEmpty {
124- let penalty = matchedIndex * Penalty. unmatchedLeadingChar
125- bestScore += max ( penalty, Penalty . maxUnmatchedLeadingChar)
126- }
127- totalScore += bestScore
128- matchedIndices. append ( matchedIndex)
129- bestScore = - 1
130- patternIndex += 1
135+ if let matchedIndex {
136+ let nextPattern = patternRunes [ safe: patternIndex + 1 ]
137+ let nextCandidate = candidateRunes [ safe: candidateIndex + 1 ]
138+ if equalFold ( nextPattern, nextCandidate) || nextCandidate == nil {
139+ if matchedIndices. isEmpty {
140+ let penalty = matchedIndex * Penalty. unmatchedLeadingChar
141+ bestScore += max ( penalty, Penalty . maxUnmatchedLeadingChar)
131142 }
143+ totalScore += bestScore
144+ matchedIndices. append ( matchedIndex)
145+ bestScore = - 1
146+ patternIndex += 1
132147 }
133-
134- lastIndex = candidateIndex
135- lastRune = candidateRune
136148 }
137149
138- let unmatchedCharactersPenalty = matchedIndices. count - candidateRunes. count
139- totalScore += unmatchedCharactersPenalty
140-
141- if matchedIndices. count == patternRunes. count {
142- matches. append ( . init( value: dataRow, index: i, matchedIndices: matchedIndices, score: totalScore) )
143- }
150+ lastIndex = candidateIndex
151+ lastRune = candidateRune
144152 }
145153
146- return matches
154+ let unmatchedCharactersPenalty = matchedIndices. count - candidateRunes. count
155+ totalScore += unmatchedCharactersPenalty
156+
157+ if matchedIndices. count == patternRunes. count {
158+ return ( matchedIndices: matchedIndices, score: totalScore)
159+ }
160+ return nil
147161}
148162
149163fileprivate func equalFold( _ lhs: Character , _ rhs: Character ) -> Bool {
0 commit comments