1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-12-22 05:01:50 +03:00
vimr/VimR/Matcher.swift

149 lines
3.5 KiB
Swift
Raw Normal View History

/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
import Foundation
class Matcher {
2016-09-25 18:50:33 +03:00
static let uppercaseCharSet = CharacterSet.uppercaseLetters
enum ExactMatchResult {
case none
case exact
case prefix
case suffix
case contains
}
2016-09-25 18:50:33 +03:00
static func exactMatchIgnoringCase(_ target: String, pattern: String) -> ExactMatchResult {
let ltarget = target.lowercased()
let lpattern = pattern.lowercased()
if ltarget == lpattern {
return .exact
}
if ltarget.hasPrefix(lpattern) {
return .prefix
}
if ltarget.hasSuffix(lpattern) {
return .suffix
}
2016-09-25 18:50:33 +03:00
if ltarget.contains(lpattern) {
return .contains
}
return .none
}
2016-09-25 18:50:33 +03:00
static func numberOfUppercaseMatches(_ target: String, pattern: String) -> Int {
var tscalars = target.unicodeScalars.filter { uppercaseCharSet.contains(UnicodeScalar($0.value)!) }
2016-09-08 21:16:37 +03:00
let count = tscalars.count
guard count > 0 else {
return 0
}
2016-09-25 18:50:33 +03:00
let pscalars = pattern.uppercased().unicodeScalars
2016-09-08 21:16:37 +03:00
pscalars.forEach { scalar in
2016-09-25 18:50:33 +03:00
if let idx = tscalars.index(of: scalar) {
tscalars.remove(at: idx)
2016-09-08 21:16:37 +03:00
}
}
return count - tscalars.count
}
/// Matches `pattern` to `target` in a fuzzy way.
/// - returns: `Array` of `Range<String.UnicodeScalarIndex>`
2016-09-25 19:53:46 +03:00
static func fuzzyIgnoringCase(_ target: String,
pattern: String) -> (matches: Int, ranges: [CountableClosedRange<Int>]) {
2016-09-25 18:50:33 +03:00
let tlower = target.lowercased()
let plower = pattern.lowercased()
let tchars = tlower.unicodeScalars
let pchars = plower.unicodeScalars
2016-09-25 18:50:33 +03:00
var flags = Array(repeating: false, count: tchars.count)
var pidx = pchars.startIndex
2016-09-25 18:50:33 +03:00
for (i, tchar) in tchars.enumerated() {
if pchars[pidx] == tchar {
flags[i] = true
2016-09-25 18:50:33 +03:00
pidx = pchars.index(after: pidx)
}
}
2016-09-25 19:53:46 +03:00
var ranges: [CountableClosedRange<Int>] = []
var matches = 0
var lastTrue = -1
var curTrue = -1
2016-09-25 18:50:33 +03:00
for (i, isTrue) in flags.enumerated() {
if isTrue {
matches = matches &+ 1
if lastTrue == -1 {
lastTrue = i
}
curTrue = i
if i == flags.count &- 1 {
if lastTrue > -1 && curTrue > -1 {
2016-09-25 19:53:46 +03:00
ranges.append(lastTrue...curTrue)
lastTrue = -1
curTrue = -1
}
}
} else {
if lastTrue > -1 && curTrue > -1 {
2016-09-25 19:53:46 +03:00
ranges.append(lastTrue...curTrue)
lastTrue = -1
curTrue = -1
}
}
}
return (matches, ranges)
}
/// Wagner-Fischer algorithm.
/// We use the 32 bit representation (`String.unicodeScalars`) of both parameters to compare them.
///
/// - returns: the distance of pattern from target
/// - seealso: https://en.wikipedia.org/wiki/WagnerFischer_algorithm
2016-09-25 18:50:33 +03:00
static func wagnerFisherDistance(_ target: String, pattern: String) -> Int {
let s = target.unicodeScalars
let t = pattern.unicodeScalars
let m = s.count
2016-09-25 18:50:33 +03:00
var prevRow = Array(repeating: 0, count: m &+ 1)
var curRow = Array(repeating: 0, count: m &+ 1)
for i in 0...m {
prevRow[i] = i
}
2016-09-25 18:50:33 +03:00
for (j, tchar) in t.enumerated() {
curRow[0] = j &+ 1
2016-09-25 18:50:33 +03:00
for (i, schar) in s.enumerated() {
if schar == tchar {
curRow[i &+ 1] = prevRow[i]
} else {
curRow[i &+ 1] = min(curRow[i] &+ 1, prevRow[i &+ 1] &+ 1, prevRow[i] &+ 1)
}
}
prevRow = curRow
}
return curRow[m]
}
}