Skip to content

Instantly share code, notes, and snippets.

@rickw
Created July 7, 2015 20:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rickw/a161cb7a75aaffbc8511 to your computer and use it in GitHub Desktop.
Save rickw/a161cb7a75aaffbc8511 to your computer and use it in GitHub Desktop.
StringScore in Swift - from the Objective-C then JavaScript repos of the same name
//
// StringScoreExtension.swift
//
//
// Created by Rick Windham on 6/23/15.
//
//
import Foundation
extension String {
struct StringScoreOptions: OptionSetType {
let rawValue: Int
init(rawValue: Int) { self.rawValue = rawValue }
static let None = StringScoreOptions(rawValue: 1)
static let FavorSmallWords = StringScoreOptions(rawValue: 2)
static let ReduceLongStringPenalty = StringScoreOptions(rawValue: 4)
}
func scoreAgainst(otherString:String, fuzziness:Double? = nil, options:StringScoreOptions = .None) -> Double {
let workingInvalidCharacterSet = NSMutableCharacterSet.lowercaseLetterCharacterSet()
workingInvalidCharacterSet.formUnionWithCharacterSet(NSCharacterSet.uppercaseLetterCharacterSet())
workingInvalidCharacterSet.addCharactersInString(" ")
let invalidCharacterSet = workingInvalidCharacterSet.invertedSet
let stringArray:NSArray = self.decomposedStringWithCanonicalMapping.componentsSeparatedByCharactersInSet(invalidCharacterSet)
let otherArray:NSArray = otherString.decomposedStringWithCanonicalMapping.componentsSeparatedByCharactersInSet(invalidCharacterSet)
var string = stringArray.componentsJoinedByString("")
let otherString = otherArray.componentsJoinedByString("")
guard string != otherString else { return 1.0 }
let otherStringLength = otherString.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)
guard otherStringLength > 0 else { return 0.0 }
var totalCharacterScore = 0.0
let stringLength = string.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)
var startOfStringBonus = false
var otherStringScore = 0.0
var fuzzies = 1.0
var finalScore = 0.0
for curIdx in otherString.startIndex..<otherString.endIndex {
var characterScore = 0.1
var indexInString:String.Index? = nil
let scanRange = Range(start: curIdx, end: advance(curIdx, 1))
let char:String = otherString.substringWithRange(scanRange)
let rangeCharLowercase:Range? = string.rangeOfString(char.lowercaseString)
let rangeCharUppercase:Range? = string.rangeOfString(char.uppercaseString)
switch(rangeCharUppercase, rangeCharLowercase) {
case (.Some(let rcu), .Some(let rcl)):
indexInString = min(rcu.startIndex, rcl.startIndex)
case (.Some(let rcu), .None):
indexInString = rcu.startIndex
case (.None, .Some(let rcl)):
indexInString = rcl.startIndex
case (.None, .None):
if let fuzz = fuzziness {
fuzzies += 1.0 - fuzz
} else {
return 0.0
}
}
if let idxInString = indexInString {
if string.substringWithRange(Range(start: idxInString, end: advance(idxInString, 1))) == char {
characterScore += 0.1
}
if idxInString == otherString.startIndex {
characterScore += 0.6
if curIdx == otherString.startIndex {
startOfStringBonus = true
}
}
if idxInString > otherString.startIndex
&& string.substringWithRange(Range(start: advance(idxInString, -1), end: idxInString)) == " " {
characterScore += 0.8
}
string = string.substringFromIndex(advance(idxInString, 1))
}
totalCharacterScore += characterScore
}
if options.contains(.FavorSmallWords) {
return totalCharacterScore / Double(stringLength)
}
otherStringScore = totalCharacterScore / Double(otherStringLength)
if options.contains(.ReduceLongStringPenalty) {
let percentageOfMatchedString:Double = Double(otherStringLength) / Double(stringLength)
let wordScore:Double = otherStringScore * percentageOfMatchedString
finalScore = (wordScore + otherStringScore) / 2.0
} else {
finalScore = ((otherStringScore * Double(otherStringLength) / Double(stringLength)) + otherStringScore) / 2.0
}
finalScore = finalScore / fuzzies
if startOfStringBonus && finalScore + 0.15 < 1 {
finalScore += 0.15;
}
return finalScore
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment