// // FuzzyMatcher.swift // Tusker // // Created by Shadowfacts on 10/10/20. // Copyright © 2020 Shadowfacts. All rights reserved. // import Foundation struct FuzzyMatcher { private init() {} /// Rudimentary string fuzzy matching algorithm. /// /// Operates on UTF-8 code points, so attempting to match strings which include characters composed of /// multiple code points may produce unexpected results. /// /// Scoring is as follows: /// +2 points for every char in `pattern` that occurs in `str` sequentially /// -2 points for every char in `pattern` that does not occur in `str` sequentially /// -1 point for every char in `str` skipped between matching chars from the `pattern` static func match(pattern: String, str: String) -> (matched: Bool, score: Int) { let pattern = pattern.lowercased() let str = str.lowercased() var patternIndex = pattern.utf8.startIndex var lastStrMatchIndex: String.UTF8View.Index? var strIndex = str.utf8.startIndex var score = 0 while patternIndex < pattern.utf8.endIndex && strIndex < str.utf8.endIndex { let patternChar = pattern.utf8[patternIndex] let strChar = str.utf8[strIndex] if patternChar == strChar { let distance = str.utf8.distance(from: lastStrMatchIndex ?? str.utf8.startIndex, to: strIndex) if distance > 1 { score -= distance - 1 } patternIndex = pattern.utf8.index(after: patternIndex) lastStrMatchIndex = strIndex strIndex = str.utf8.index(after: strIndex) score += 2 } else { strIndex = str.utf8.index(after: strIndex) if strIndex >= str.utf8.endIndex { patternIndex = pattern.utf8.index(after: patternIndex) strIndex = str.utf8.index(after: lastStrMatchIndex ?? str.utf8.startIndex) score -= 2 } } } return (score > 0, score) } }