Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Use FZF backend instead #31

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions NSString+Score.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// NSString+Score.h
//
// Created by Nicholas Bruning on 5/12/11.
// Copyright (c) 2011 Involved Pty Ltd. All rights reserved.
//

#import <Foundation/Foundation.h>

enum{
NSStringScoreOptionNone = 1 << 0,
NSStringScoreOptionFavorSmallerWords = 1 << 1,
NSStringScoreOptionReducedLongStringPenalty = 1 << 2
};

typedef NSUInteger NSStringScoreOption;

@interface NSString (Score)

- (CGFloat) scoreAgainst:(NSString *)otherString;
- (CGFloat) scoreAgainst:(NSString *)otherString fuzziness:(NSNumber *)fuzziness;
- (CGFloat) scoreAgainst:(NSString *)otherString fuzziness:(NSNumber *)fuzziness options:(NSStringScoreOption)options;

- (CGFloat) scoreAgainst:(NSString *)anotherString fuzziness:(NSNumber *)fuzziness options:(NSStringScoreOption)options
invalidCharacterSet:(NSCharacterSet *)invalidCharacterSet decomposedString:(NSString *)string;

- (NSString *)decomposedStringWithInvalidCharacterSet:(NSCharacterSet *)invalidCharacterSet;
- (NSCharacterSet *)invalidCharacterSet;

@end
164 changes: 164 additions & 0 deletions NSString+Score.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
//
// NSString+Score.m
//
// Created by Nicholas Bruning on 5/12/11.
// Copyright (c) 2011 Involved Pty Ltd. All rights reserved.
//

//score reference: http://jsfiddle.net/JrLVD/

#import "NSString+Score.h"

@implementation NSString (Score)

- (CGFloat) scoreAgainst:(NSString *)otherString{
return [self scoreAgainst:otherString fuzziness:nil];
}

- (CGFloat) scoreAgainst:(NSString *)otherString fuzziness:(NSNumber *)fuzziness{
return [self scoreAgainst:otherString fuzziness:fuzziness options:NSStringScoreOptionNone];
}

- (CGFloat) scoreAgainst:(NSString *)anotherString fuzziness:(NSNumber *)fuzziness options:(NSStringScoreOption)options {
NSCharacterSet *invalidCharacterSet = [self invalidCharacterSet];
NSString *string = [self decomposedStringWithInvalidCharacterSet:invalidCharacterSet];
return [self scoreAgainst:anotherString fuzziness:fuzziness options:options invalidCharacterSet:invalidCharacterSet decomposedString:string];
}

- (NSCharacterSet *)invalidCharacterSet {
NSMutableCharacterSet *workingInvalidCharacterSet = [NSMutableCharacterSet lowercaseLetterCharacterSet];
[workingInvalidCharacterSet formUnionWithCharacterSet:[NSCharacterSet uppercaseLetterCharacterSet]];
[workingInvalidCharacterSet addCharactersInString:@" "];
NSCharacterSet *invalidCharacterSet = [workingInvalidCharacterSet invertedSet];
return invalidCharacterSet;
}

- (NSString *)decomposedStringWithInvalidCharacterSet:(NSCharacterSet *)invalidCharacterSet {
NSString *string = [[[self decomposedStringWithCanonicalMapping] componentsSeparatedByCharactersInSet:invalidCharacterSet] componentsJoinedByString:@""];
return string;
}

- (CGFloat) scoreAgainst:(NSString *)anotherString fuzziness:(NSNumber *)fuzziness options:(NSStringScoreOption)options
invalidCharacterSet:(NSCharacterSet *)invalidCharacterSet decomposedString:(NSString *)string {
NSString *otherString = [[[anotherString decomposedStringWithCanonicalMapping] componentsSeparatedByCharactersInSet:invalidCharacterSet] componentsJoinedByString:@""];

// If the string is equal to the abbreviation, perfect match.
if([string isEqualToString:otherString]) return (CGFloat) 1.0f;

//if it's not a perfect match and is empty return 0
if([otherString length] == 0) return (CGFloat) 0.0f;

CGFloat totalCharacterScore = 0;
NSUInteger otherStringLength = [otherString length];
NSUInteger stringLength = [string length];
BOOL startOfStringBonus = NO;
CGFloat otherStringScore;
CGFloat fuzzies = 1;
CGFloat finalScore;

NSString *otherUpper = [otherString uppercaseString];
NSString *otherLower = [otherString lowercaseString];

CGFloat fuzzinessFloat = fuzziness.floatValue;

unichar space = [@" " characterAtIndex:0];

// Walk through abbreviation and add up scores.
for(uint index = 0; index < otherStringLength; index++){
CGFloat characterScore = 0.1;
NSInteger indexInString = NSNotFound;
NSRange rangeChrLowercase;
NSRange rangeChrUppercase;

NSRange r =NSMakeRange(index, 1);

unichar chr = [otherString characterAtIndex:index];

NSString *upperChr = [otherUpper substringWithRange:r];
NSString *lowerChr = [otherLower substringWithRange:r];

//make these next few lines leverage NSNotfound, methinks.
rangeChrLowercase = [string rangeOfString:lowerChr];
rangeChrUppercase = [string rangeOfString:upperChr];

if(rangeChrLowercase.location == NSNotFound && rangeChrUppercase.location == NSNotFound){
if(fuzziness){
fuzzies += 1 - fuzzinessFloat;
} else {
return 0; // this is an error!
}

} else if (rangeChrLowercase.location != NSNotFound && rangeChrUppercase.location != NSNotFound){
indexInString = MIN(rangeChrLowercase.location, rangeChrUppercase.location);

} else if(rangeChrLowercase.location != NSNotFound || rangeChrUppercase.location != NSNotFound){
indexInString = rangeChrLowercase.location != NSNotFound ? rangeChrLowercase.location : rangeChrUppercase.location;

} else {
indexInString = MIN(rangeChrLowercase.location, rangeChrUppercase.location);

}

// Set base score for matching chr

// Same case bonus.
if(indexInString != NSNotFound && [string characterAtIndex:indexInString] == chr){
characterScore += 0.1;
}

// Consecutive letter & start-of-string bonus
if(indexInString == 0){
// Increase the score when matching first character of the remainder of the string
characterScore += 0.6;
if(index == 0){
// If match is the first character of the string
// & the first character of abbreviation, add a
// start-of-string match bonus.
startOfStringBonus = YES;
}
} else if(indexInString != NSNotFound) {
// Acronym Bonus
// Weighing Logic: Typing the first character of an acronym is as if you
// preceded it with two perfect character matches.

if([string characterAtIndex:indexInString - 1] == space) {
characterScore += 0.8;
}
}

// Left trim the already matched part of the string
// (forces sequential matching).
if(indexInString != NSNotFound){
string = [string substringFromIndex:indexInString + 1];
}

totalCharacterScore += characterScore;
}

if(NSStringScoreOptionFavorSmallerWords == (options & NSStringScoreOptionFavorSmallerWords)){
// Weigh smaller words higher
return totalCharacterScore / stringLength;
}

otherStringScore = totalCharacterScore / otherStringLength;

if(NSStringScoreOptionReducedLongStringPenalty == (options & NSStringScoreOptionReducedLongStringPenalty)){
// Reduce the penalty for longer words
CGFloat percentageOfMatchedString = otherStringLength / stringLength;
CGFloat wordScore = otherStringScore * percentageOfMatchedString;
finalScore = (wordScore + otherStringScore) / 2;

} else {
finalScore = ((otherStringScore * ((CGFloat)(otherStringLength) / (CGFloat)(stringLength))) + otherStringScore) / 2;
}

finalScore = finalScore / fuzzies;

if(startOfStringBonus && finalScore + 0.15 < 1){
finalScore += 0.15;
}

return finalScore;
}

@end
Loading