mirror of
https://github.com/kiwix/kiwix-apple.git
synced 2025-09-22 19:19:22 -04:00
Search Performance Optimization
This commit is contained in:
parent
8e0eb87959
commit
70f3c9d777
@ -9,9 +9,10 @@
|
||||
import UIKit
|
||||
|
||||
class SettingTBVC: UITableViewController {
|
||||
private(set) var sectionHeader = [LocalizedStrings.library, LocalizedStrings.reading,LocalizedStrings.misc]
|
||||
private(set) var sectionHeader = [LocalizedStrings.library, LocalizedStrings.reading, LocalizedStrings.search, LocalizedStrings.misc]
|
||||
private(set) var cellTextlabels = [[LocalizedStrings.libraryAutoRefresh, LocalizedStrings.libraryUseCellularData, LocalizedStrings.libraryBackup],
|
||||
[LocalizedStrings.fontSize, LocalizedStrings.adjustLayout],
|
||||
[LocalizedStrings.history],
|
||||
[LocalizedStrings.rateKiwix, LocalizedStrings.about]]
|
||||
|
||||
let dateComponentsFormatter: NSDateComponentsFormatter = {
|
||||
@ -27,8 +28,7 @@ class SettingTBVC: UITableViewController {
|
||||
showRateKiwixIfNeeded()
|
||||
|
||||
if UIApplication.buildStatus == .Alpha {
|
||||
sectionHeader.append("Search")
|
||||
cellTextlabels.append(["Boost Factor 🚀"])
|
||||
cellTextlabels[2].append("Boost Factor 🚀")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
import UIKit
|
||||
import DZNEmptyDataSet
|
||||
|
||||
class TableOfContentsController: UIViewController, UITableViewDelegate, UITableViewDataSource ,DZNEmptyDataSetSource, DZNEmptyDataSetDelegate {
|
||||
class TableOfContentsController: UIViewController, UITableViewDelegate, UITableViewDataSource, DZNEmptyDataSetSource, DZNEmptyDataSetDelegate {
|
||||
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
weak var delegate: TableOfContentsDelegate?
|
||||
|
@ -36,7 +36,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.6.1529</string>
|
||||
<string>1.6.1563</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
@ -42,7 +42,7 @@ enum BuildStatus {
|
||||
extension UIApplication {
|
||||
class var buildStatus: BuildStatus {
|
||||
get {
|
||||
return .Alpha
|
||||
return .Beta
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import PSOperations
|
||||
class SearchOperation: GroupOperation {
|
||||
let completionHandler: ([SearchResult]?) -> Void
|
||||
private(set) var results = [SearchResult]()
|
||||
//private let startTime = NSDate()
|
||||
|
||||
init(searchTerm: String, completionHandler: ([SearchResult]?) -> Void) {
|
||||
self.completionHandler = completionHandler
|
||||
@ -26,7 +27,7 @@ class SearchOperation: GroupOperation {
|
||||
guard let book = Book.fetch(id, context: managedObjectContext) else {continue}
|
||||
guard book.includeInSearch else {continue}
|
||||
let operation = SingleBookSearchOperation(zimReader: zimReader,
|
||||
searchTerm: searchTerm.lowercaseString,
|
||||
lowerCaseSearchTerm: searchTerm.lowercaseString,
|
||||
completionHandler: { [unowned sortOperation] (results) in
|
||||
sortOperation.results += results
|
||||
})
|
||||
@ -41,7 +42,7 @@ class SearchOperation: GroupOperation {
|
||||
}
|
||||
|
||||
override func finished(errors: [NSError]) {
|
||||
// print("Search Operation finished, status \(cancelled ? "Canceled" : "Not Canceled")")
|
||||
//print("Search Operation finished, status \(cancelled ? "Canceled" : "Not Canceled"), \(NSDate().timeIntervalSinceDate(startTime))")
|
||||
NSOperationQueue.mainQueue().addOperationWithBlock {
|
||||
self.completionHandler(self.cancelled ? nil : self.results)
|
||||
}
|
||||
@ -50,22 +51,22 @@ class SearchOperation: GroupOperation {
|
||||
|
||||
private class SingleBookSearchOperation: Operation {
|
||||
let zimReader: ZimReader
|
||||
let searchTerm: String
|
||||
let lowerCaseSearchTerm: String
|
||||
let completionHandler: ([SearchResult]) -> Void
|
||||
|
||||
init(zimReader: ZimReader, searchTerm: String, completionHandler: ([SearchResult]) -> Void) {
|
||||
init(zimReader: ZimReader, lowerCaseSearchTerm: String, completionHandler: ([SearchResult]) -> Void) {
|
||||
self.zimReader = zimReader
|
||||
self.searchTerm = searchTerm
|
||||
self.lowerCaseSearchTerm = lowerCaseSearchTerm
|
||||
self.completionHandler = completionHandler
|
||||
}
|
||||
|
||||
override private func execute() {
|
||||
var results = [String: SearchResult]()
|
||||
let indexedDics = zimReader.searchUsingIndex(searchTerm) as? [[String: AnyObject]] ?? [[String: AnyObject]]()
|
||||
let titleDics = zimReader.searchSuggestionsSmart(searchTerm) as? [[String: AnyObject]] ?? [[String: AnyObject]]()
|
||||
let indexedDics = zimReader.searchUsingIndex(lowerCaseSearchTerm) as? [[String: AnyObject]] ?? [[String: AnyObject]]()
|
||||
let titleDics = zimReader.searchSuggestionsSmart(lowerCaseSearchTerm) as? [[String: AnyObject]] ?? [[String: AnyObject]]()
|
||||
let mixedDics = titleDics + indexedDics // It is important we process the title search result first, so that we always keep the indexed search result
|
||||
for dic in mixedDics {
|
||||
guard let result = SearchResult (rawResult: dic) else {continue}
|
||||
guard let result = SearchResult (rawResult: dic, lowerCaseSearchTerm: lowerCaseSearchTerm) else {continue}
|
||||
results[result.path] = result
|
||||
}
|
||||
completionHandler(Array(results.values))
|
||||
|
@ -63,6 +63,7 @@ class LocalizedStrings {
|
||||
class var remove: String {return NSLocalizedString("Remove", comment: "Basic")}
|
||||
class var delete: String {return NSLocalizedString("Delete", comment: "Basic")}
|
||||
class var refreshing: String {return NSLocalizedString("Refreshing...", comment: "Basic")}
|
||||
class var history: String {return NSLocalizedString("History", comment: "Basic")}
|
||||
|
||||
// MARK: - OS X
|
||||
class var General: String {return NSLocalizedString("General", comment: "OS X, Preference")}
|
||||
|
@ -8,6 +8,9 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
typealias ZimID = String
|
||||
typealias ArticlePath = String
|
||||
|
||||
extension ZimReader {
|
||||
var metaData: [String: AnyObject] {
|
||||
var metadata = [String: AnyObject]()
|
||||
@ -28,5 +31,46 @@ extension ZimReader {
|
||||
}
|
||||
}
|
||||
|
||||
typealias ZimID = String
|
||||
typealias ArticlePath = String
|
||||
// https://gist.github.com/adamyanalunas/69f6601fad6040686d300a1cdc20f500
|
||||
private extension String {
|
||||
subscript(index: Int) -> Character {
|
||||
return self[startIndex.advancedBy(index)]
|
||||
}
|
||||
|
||||
subscript(range: Range<Int>) -> String {
|
||||
let start = startIndex.advancedBy(range.startIndex)
|
||||
let end = startIndex.advancedBy(range.endIndex)
|
||||
return self[start..<end]
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
func levenshtein(string cmpString: String) -> Int {
|
||||
let (length, cmpLength) = (characters.count, cmpString.characters.count)
|
||||
var matrix = Array(
|
||||
count: cmpLength + 1,
|
||||
repeatedValue: Array(
|
||||
count: length + 1,
|
||||
repeatedValue: 0
|
||||
)
|
||||
)
|
||||
|
||||
for m in 1..<cmpLength {
|
||||
matrix[m][0] = matrix[m - 1][0] + 1
|
||||
}
|
||||
|
||||
for n in 1..<length {
|
||||
matrix[0][n] = matrix[0][n - 1] + 1
|
||||
}
|
||||
|
||||
for m in 1..<(cmpLength + 1) {
|
||||
for n in 1..<(length + 1) {
|
||||
let penalty = self[n - 1] == cmpString[m - 1] ? 0 : 1
|
||||
let (horizontal, vertical, diagonal) = (matrix[m - 1][n] + 1, matrix[m][n - 1] + 1, matrix[m - 1][n - 1])
|
||||
matrix[m][n] = min(horizontal, vertical, diagonal + penalty)
|
||||
}
|
||||
}
|
||||
|
||||
return matrix[cmpLength][length]
|
||||
}
|
||||
}
|
@ -9,13 +9,18 @@
|
||||
import UIKit
|
||||
|
||||
class SearchResult: CustomStringConvertible {
|
||||
let lowerCaseSearchTerm: String
|
||||
let title: String
|
||||
let path: ArticlePath
|
||||
let bookID: ZimID
|
||||
let snippet: String?
|
||||
|
||||
let probability: Double? // range: 0.0 - 1.0
|
||||
let distance: Int // Levenshtein distance, non negative integer
|
||||
private(set) lazy var distance: Int = {
|
||||
// Here we dont use the swift version of levenshtein, because it is slower than the C++ implementation
|
||||
//return self.title.lowercaseString.levenshtein(string: self.lowerCaseSearchTerm)
|
||||
return ZimReader.levenshtein(self.title.lowercaseString, anotherString: self.lowerCaseSearchTerm)
|
||||
}()
|
||||
private(set) lazy var score: Double = {
|
||||
if let probability = self.probability {
|
||||
return WeightFactor.calculate(probability) * Double(self.distance)
|
||||
@ -24,13 +29,13 @@ class SearchResult: CustomStringConvertible {
|
||||
}
|
||||
}()
|
||||
|
||||
init?(rawResult: [String: AnyObject]) {
|
||||
init?(rawResult: [String: AnyObject], lowerCaseSearchTerm: String) {
|
||||
self.lowerCaseSearchTerm = lowerCaseSearchTerm
|
||||
let title = (rawResult["title"] as? String) ?? ""
|
||||
let path = (rawResult["path"] as? String) ?? ""
|
||||
let bookID = (rawResult["bookID"] as? ZimID) ?? ""
|
||||
let snippet = rawResult["snippet"] as? String
|
||||
|
||||
let distance = (rawResult["distance"]as? NSNumber)?.integerValue ?? title.characters.count
|
||||
let probability: Double? = {
|
||||
if let probability = (rawResult["probability"] as? NSNumber)?.doubleValue {
|
||||
return probability / 100.0
|
||||
@ -44,7 +49,6 @@ class SearchResult: CustomStringConvertible {
|
||||
self.bookID = bookID
|
||||
self.snippet = snippet
|
||||
self.probability = probability
|
||||
self.distance = distance
|
||||
|
||||
if title == "" || path == "" || bookID == "" {return nil}
|
||||
}
|
||||
|
@ -54,4 +54,6 @@
|
||||
|
||||
- (NSURL *)fileURL;
|
||||
|
||||
+ (NSInteger)levenshtein:(NSString *)string anotherString:(NSString *)anotherString;
|
||||
|
||||
@end
|
||||
|
@ -57,11 +57,9 @@
|
||||
while (_reader->getNextSuggestion(titleC)) {
|
||||
NSString *title = [NSString stringWithUTF8String:titleC.c_str()];
|
||||
NSString *path = [self pageURLFromTitle:title];
|
||||
NSNumber *distance = [NSNumber numberWithInteger:[self levenshteinDistance:searchTerm andString:title.lowercaseString]];
|
||||
[results addObject:@{@"title": title,
|
||||
@"path": path,
|
||||
@"bookID": bookID,
|
||||
@"distance": distance}];
|
||||
@"bookID": bookID}];
|
||||
}
|
||||
}
|
||||
return results;
|
||||
@ -99,13 +97,11 @@
|
||||
NSString *path = [NSString stringWithUTF8String:doc.get_data().c_str()];
|
||||
NSString *title = [NSString stringWithUTF8String:doc.get_value(0).c_str()];
|
||||
NSString *snippet = [NSString stringWithUTF8String:doc.get_value(1).c_str()];
|
||||
NSNumber *distance = [NSNumber numberWithInteger:[self levenshteinDistance:searchTerm andString:title.lowercaseString]];
|
||||
|
||||
NSDictionary *result = @{@"title": title,
|
||||
@"path": path,
|
||||
@"bookID": bookID,
|
||||
@"probability": percent,
|
||||
@"distance": distance,
|
||||
@"snippet": snippet};
|
||||
[results addObject:result];
|
||||
}
|
||||
@ -343,4 +339,10 @@ int levenshtein_distance(const std::string &s1, const std::string &s2)
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSInteger)levenshtein:(NSString *)strA anotherString:(NSString *)strB {
|
||||
const string str1 = [strA cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
const string str2 = [strB cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
return levenshtein_distance(str1, str2);
|
||||
}
|
||||
|
||||
@end
|
||||
|
Loading…
x
Reference in New Issue
Block a user