Skip to content

Commit

Permalink
Merge pull request #21 from yichizhang/dev
Browse files Browse the repository at this point in the history
Support diacritics
  • Loading branch information
yichizhang authored May 31, 2018
2 parents 0b286b7 + 26e4caf commit 0e48809
Show file tree
Hide file tree
Showing 12 changed files with 840 additions and 692 deletions.
186 changes: 101 additions & 85 deletions Source/StringScore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Based on string_score 0.1.21 by Joshaven Potter.
// https://github.com/joshaven/string_score/
//
// Copyright (c) 2016 YICHI ZHANG
// Copyright (c) 2016-present YICHI ZHANG
// https://github.com/yichizhang
// [email protected]
//
Expand All @@ -27,97 +27,113 @@

import Foundation

private extension String {

func charAt(_ i: Int) -> Character {
let index = self.index(self.startIndex, offsetBy: i)
return self[index]
}

func charStrAt(_ i: Int) -> String {
return String(charAt(i))
}
}

public extension String {

func score(word: String, fuzziness: Double? = nil) -> Double {

// If the string is equal to the word, perfect match.
if self == word {
return 1
}

//if it's not a perfect match and is empty return 0
if word.isEmpty || self.isEmpty {
return 0
}

var runningScore = 0.0
var charScore = 0.0
var finalScore = 0.0

let string = self
let lString = string.lowercased()
let strLength = string.count
let lWord = word.lowercased()
let wordLength = word.count

var idxOf: String.Index!
var startAt = lString.startIndex
var fuzzies = 1.0
var fuzzyFactor = 0.0
var fuzzinessIsNil = true

// Cache fuzzyFactor for speed increase
if let fuzziness = fuzziness {
fuzzyFactor = 1 - fuzziness
fuzzinessIsNil = false
}

func score(word: String, fuzziness: Double? = nil) -> Double {
for i in 0 ..< wordLength {
// Find next first case-insensitive match of word's i-th character.
// The search in "string" begins at "startAt".

if let range = lString.range(
of: lWord.charStrAt(i),
options: [.caseInsensitive, .diacriticInsensitive],
range: Range<String.Index>(startAt..<lString.endIndex),
locale: nil
) {

// If the string is equal to the word, perfect match.
if self == word {
return 1
}
// start index of word's i-th character in string.
idxOf = range.lowerBound

//if it's not a perfect match and is empty return 0
if word.isEmpty || self.isEmpty {
return 0
if startAt == idxOf {
// Consecutive letter & start-of-string Bonus
charScore = 0.7
}

var
runningScore = 0.0,
charScore = 0.0,
finalScore = 0.0,
string = self,
lString = string.lowercased(),
strLength = string.count,
lWord = word.lowercased(),
wordLength = word.count,
idxOf: String.Index!,
startAt = lString.startIndex,
fuzzies = 1.0,
fuzzyFactor = 0.0,
fuzzinessIsNil = true

// Cache fuzzyFactor for speed increase
if let fuzziness = fuzziness {
fuzzyFactor = 1 - fuzziness
fuzzinessIsNil = false
else {
charScore = 0.1

// Acronym Bonus
// Weighing Logic: Typing the first character of an acronym is as if you
// preceded it with two perfect character matches.
if string[string.index(before: idxOf)] == " " {
charScore += 0.8
}
}

for i in 0 ..< wordLength {
// Find next first case-insensitive match of word's i-th character.
// The search in "string" begins at "startAt".

if let range = lString.range(of:
String(lWord[lWord.index(lWord.startIndex, offsetBy: i)] as Character),
options: NSString.CompareOptions.caseInsensitive,
range: Range<String.Index>(startAt..<lString.endIndex),
locale: nil
) {
// start index of word's i-th character in string.
idxOf = range.lowerBound
if startAt == idxOf {
// Consecutive letter & start-of-string Bonus
charScore = 0.7
}
else {
charScore = 0.1

// Acronym Bonus
// Weighing Logic: Typing the first character of an acronym is as if you
// preceded it with two perfect character matches.
if string[string.index(idxOf, offsetBy: -1)] == " " {
charScore += 0.8
}
}
}
else {
// Character not found.
if fuzzinessIsNil {
// Fuzziness is nil. Return 0.
return 0
}
else {
fuzzies += fuzzyFactor
continue
}
}

// Same case bonus.
if (string[idxOf] == word[word.index(word.startIndex, offsetBy: i)]) {
charScore += 0.1
}

// Update scores and startAt position for next round of indexOf
runningScore += charScore
startAt = string.index(idxOf, offsetBy: 1)
}
else {
// Character not found.
if fuzzinessIsNil {
// Fuzziness is nil. Return 0.
return 0
}

// Reduce penalty for longer strings.
finalScore = 0.5 * (runningScore / Double(strLength) + runningScore / Double(wordLength)) / fuzzies

if (lWord[lWord.startIndex] == lString[lString.startIndex]) && (finalScore < 0.85) {
finalScore += 0.15
else {
fuzzies += fuzzyFactor
continue
}

return finalScore
}

// Same case bonus.
if (string[idxOf] == word[word.index(word.startIndex, offsetBy: i)]) {
charScore += 0.1
}

// Update scores and startAt position for next round of indexOf
runningScore += charScore
startAt = string.index(idxOf, offsetBy: 1)
}

// Reduce penalty for longer strings.
finalScore = 0.5 * (runningScore / Double(strLength) + runningScore / Double(wordLength)) / fuzzies

if (finalScore < 0.85) &&
(lWord.charStrAt(0).compare(lString.charStrAt(0), options: .diacriticInsensitive) == .orderedSame) {
finalScore += 0.15
}

return finalScore
}
}
27 changes: 26 additions & 1 deletion StringScore.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@
attributes = {
LastSwiftMigration = 0720;
LastSwiftUpdateCheck = 0730;
LastUpgradeCheck = 0720;
LastUpgradeCheck = 0930;
ORGANIZATIONNAME = "YICHI ZHANG";
TargetAttributes = {
12833C291A97A00C00440F55 = {
Expand Down Expand Up @@ -382,13 +382,23 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
Expand All @@ -398,6 +408,7 @@
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
Expand Down Expand Up @@ -427,13 +438,23 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
Expand All @@ -442,6 +463,7 @@
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
Expand All @@ -451,6 +473,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 8.3;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
Expand Down Expand Up @@ -489,6 +512,7 @@
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
Expand All @@ -514,6 +538,7 @@
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0910"
LastUpgradeVersion = "0930"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand All @@ -26,7 +26,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
Expand All @@ -46,7 +45,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0910"
LastUpgradeVersion = "0930"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand All @@ -26,7 +26,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
Expand Down Expand Up @@ -56,7 +55,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
Expand Down
12 changes: 6 additions & 6 deletions StringScoreDemo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
import UIKit

@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

return true
}
return true
}
}
Loading

0 comments on commit 0e48809

Please sign in to comment.