forked from shadowfacts/Tusker
63 lines
2.2 KiB
Swift
63 lines
2.2 KiB
Swift
//
|
|
// 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)
|
|
}
|
|
|
|
}
|