@@ -141,30 +141,94 @@ extension StringProtocol where SubSequence == Substring {
141
141
return Swift . max ( rows, columns)
142
142
}
143
143
144
- var matrix = Array ( repeating: Array ( repeating: 0 , count: columns + 1 ) , count: rows + 1 )
145
-
146
- for row in 1 ... rows {
147
- matrix [ row] [ 0 ] = row
144
+ // Trim common prefix and suffix
145
+ var selfStartTrim = self . startIndex
146
+ var targetStartTrim = target. startIndex
147
+ while selfStartTrim < self . endIndex &&
148
+ targetStartTrim < target. endIndex &&
149
+ self [ selfStartTrim] == target [ targetStartTrim] {
150
+ self . formIndex ( after: & selfStartTrim)
151
+ target. formIndex ( after: & targetStartTrim)
148
152
}
149
- for column in 1 ... columns {
150
- matrix [ 0 ] [ column] = column
153
+
154
+ var selfEndTrim = self . endIndex
155
+ var targetEndTrim = target. endIndex
156
+
157
+ while selfEndTrim > selfStartTrim &&
158
+ targetEndTrim > targetStartTrim {
159
+ let selfIdx = self . index ( before: selfEndTrim)
160
+ let targetIdx = self . index ( before: targetEndTrim)
161
+
162
+ guard self [ selfIdx] == target [ targetIdx] else {
163
+ break
164
+ }
165
+
166
+ self . formIndex ( before: & selfEndTrim)
167
+ target. formIndex ( before: & targetEndTrim)
168
+ }
169
+
170
+ // Equal strings
171
+ guard !( selfStartTrim == self . endIndex &&
172
+ targetStartTrim == target. endIndex) else {
173
+ return 0
151
174
}
152
175
153
- for row in 1 ... rows {
154
- for column in 1 ... columns {
155
- let source = self [ self . index ( self . startIndex, offsetBy: row - 1 ) ]
156
- let target = target [ target. index ( target. startIndex, offsetBy: column - 1 ) ]
157
- let cost = source == target ? 0 : 1
158
-
159
- matrix [ row] [ column] = Swift . min (
160
- matrix [ row - 1 ] [ column] + 1 ,
161
- matrix [ row] [ column - 1 ] + 1 ,
162
- matrix [ row - 1 ] [ column - 1 ] + cost
163
- )
176
+ // After trimming common prefix and suffix, self is empty.
177
+ guard selfStartTrim < selfEndTrim else {
178
+ return target. distance ( from: targetStartTrim,
179
+ to: targetEndTrim)
180
+ }
181
+
182
+ // After trimming common prefix and suffix, target is empty.
183
+ guard targetStartTrim < targetEndTrim else {
184
+ return distance ( from: selfStartTrim,
185
+ to: selfEndTrim)
186
+ }
187
+
188
+ let newSelf = self [ selfStartTrim..< selfEndTrim]
189
+ let newTarget = target [ targetStartTrim..< targetEndTrim]
190
+
191
+ let m = newSelf. count
192
+ let n = newTarget. count
193
+
194
+ // Initialize the levenshtein matrix with only two rows
195
+ // current and previous.
196
+ var previousRow = [ Int] ( repeating: 0 , count: n + 1 )
197
+ var currentRow = [ Int] ( unsafeUninitializedCapacity: n + 1 ) {
198
+ buffer, resultCount in
199
+ for offset in 0 ... n {
200
+ var ref = buffer. baseAddress!
201
+ ref += offset
202
+ ref. initialize ( to: offset)
203
+ resultCount &+= 1
164
204
}
165
205
}
166
-
167
- return matrix. last!. last!
206
+
207
+ var sourceIdx = newSelf. startIndex
208
+ for i in 1 ... m {
209
+ swap ( & previousRow, & currentRow)
210
+ currentRow [ 0 ] = i
211
+
212
+ var targetIdx = newTarget. startIndex
213
+ for j in 1 ... n {
214
+ // If characteres are equal for the levenshtein algorithm the
215
+ // minimum will always be the substitution cost, so we can fast
216
+ // path here in order to avoid min calls.
217
+ if newSelf [ sourceIdx] == newTarget [ targetIdx] {
218
+ currentRow [ j] = previousRow [ j - 1 ]
219
+ } else {
220
+ let deletion = previousRow [ j]
221
+ let insertion = currentRow [ j - 1 ]
222
+ let substitution = previousRow [ j - 1 ]
223
+ currentRow [ j] = Swift . min ( deletion, Swift . min ( insertion, substitution) ) + 1
224
+ }
225
+ // j += 1
226
+ newTarget. formIndex ( after: & targetIdx)
227
+ }
228
+ // i += 1
229
+ newSelf. formIndex ( after: & sourceIdx)
230
+ }
231
+ return currentRow [ n]
168
232
}
169
233
170
234
func indentingEachLine( by n: Int ) -> String {
0 commit comments