development
swift 52 lines 1.72 KB
Raw
1 import Foundation
2
3 final class SearchService {
4 private let repository: ContentRepository
5
6 init(repository: ContentRepository) {
7 self.repository = repository
8 }
9
10 func results(for query: String) -> [GuideEntry] {
11 let trimmedQuery = query.trimmingCharacters(in: .whitespacesAndNewlines)
12 guard !trimmedQuery.isEmpty else { return [] }
13
14 let needle = trimmedQuery.lowercased()
15
16 let rankedEntries: [(entry: GuideEntry, score: Int)] = repository.entries.compactMap {
17 entry in
18 let score = score(for: entry, needle: needle)
19 guard score > 0 else { return nil }
20 return (entry: entry, score: score)
21 }
22
23 return
24 rankedEntries
25 .sorted { lhs, rhs in
26 if lhs.score == rhs.score {
27 return lhs.entry.title < rhs.entry.title
28 }
29 return lhs.score > rhs.score
30 }
31 .map(\.entry)
32 }
33
34 func suggestions(limit: Int = 6) -> [GuideEntry] {
35 Array(repository.featuredEntries.prefix(limit))
36 }
37
38 private func score(for entry: GuideEntry, needle: String) -> Int {
39 let title = entry.title.lowercased()
40 let subtitle = entry.subtitle.lowercased()
41
42 if title == needle { return 120 }
43 if entry.aliases.map({ $0.lowercased() }).contains(needle) { return 100 }
44 if title.hasPrefix(needle) { return 90 }
45 if title.contains(needle) { return 80 }
46 if subtitle.contains(needle) { return 60 }
47 if entry.tags.contains(where: { $0.lowercased().contains(needle) }) { return 45 }
48 if entry.searchTokens.contains(where: { $0.lowercased().contains(needle) }) { return 30 }
49
50 return 0
51 }
52 }