From b01ec5ec67eab93cecae4fd8f67b1a7a95109760 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 18 Mar 2024 13:09:57 +0100 Subject: [PATCH] Improved the overall implementation of the FilterWordsUseCase use case in the Filter library. --- .../Use Cases/FilterWordsUseCase.swift | 93 ++++++++----------- .../Use Cases/FilterWordsUseCaseTests.swift | 57 ++++++------ 2 files changed, 71 insertions(+), 79 deletions(-) diff --git a/Libraries/Filter/Kit/Sources/Use Cases/FilterWordsUseCase.swift b/Libraries/Filter/Kit/Sources/Use Cases/FilterWordsUseCase.swift index b158f3f..8f45e40 100644 --- a/Libraries/Filter/Kit/Sources/Use Cases/FilterWordsUseCase.swift +++ b/Libraries/Filter/Kit/Sources/Use Cases/FilterWordsUseCase.swift @@ -1,18 +1,19 @@ // // FilterWordsUseCase.swift -// ReviewsParserKit +// ReviewsFilterKit // // Created by Javier Cicchelli on 17/03/2024. // Copyright © 2024 Röck+Cöde VoF. All rights reserved. // import Foundation +import ReviewsFoundationKit public struct FilterWordsUseCase { // MARK: Type aliases public typealias Input = String - public typealias Output = [Tuple] + public typealias Output = [WordCount] // MARK: Initialisers public init() {} @@ -32,42 +33,50 @@ public struct FilterWordsUseCase { guard !matches.isEmpty else { return [] } - let wordsAll = matches.compactMap { + let terms = matches.compactMap { Range($0.range, in: input).map { String(input[$0]) } } + + let wordsAll = terms + .map { ($0, $0.folding(options: .caseInsensitive, locale: nil)) } + .map(Word.init) + + let wordsCount = wordsAll.map { word -> WordCount in + .init( + word: word, + count: terms.filter { $0 == word.term }.count + ) + }.reduce(into: [WordCount]()) { partialResult, wordCount in + guard partialResult.filter({ $0 == wordCount }).isEmpty else { return } + + partialResult.append(wordCount) + } - let wordsUnique = wordsAll - .reduce(into: [String]()) { partialResult, word in - guard partialResult - .filter({ compareWords(word, $0) }) - .isEmpty - else { return } - - partialResult.append( - word.folding( - options: [ - .caseInsensitive, - .diacriticInsensitive - ], - locale: .current - ) - .capitalized - ) - } - - return Dictionary(grouping: wordsUnique) { word in + let wordsUnique = Dictionary(grouping: wordsCount) { wordCount -> String in wordsAll - .filter { compareWords(word, $0) } - .count + .filter { $0.token == wordCount.word.token } + .map(\.token) + .first ?? .empty + }.map { (key, values) -> WordCount in + .init( + word: .init( + term: key.capitalized, + token: key + ), + count: values + .map(\.count) + .reduce(0, +) + ) } - .flatMap { (key, values) -> Output in - values.map { - .init(word: $0, count: key) - } - } - .sorted { + + print("WORD TERMS: \(terms)") + print("WORD ALL: \(wordsAll)") + print("WORD COUNT: \(wordsCount)") + print("WORD UNIQUE: \(wordsUnique)") + + return wordsUnique.sorted { guard $0.count != $1.count else { - return $0.word < $1.word + return $0.word.token < $1.word.token } return $0.count > $1.count @@ -92,26 +101,6 @@ private extension FilterWordsUseCase { } -// MARK: - Structs -extension FilterWordsUseCase { - public struct Tuple: Equatable { - - // MARK: Constants - let word: String - let count: Int - - // MARK: Initialisers - public init( - word: String, - count: Int - ) { - self.word = word - self.count = count - } - - } -} - // MARK: - String+Constants private extension String { enum Pattern { diff --git a/Libraries/Filter/Test/Tests/Use Cases/FilterWordsUseCaseTests.swift b/Libraries/Filter/Test/Tests/Use Cases/FilterWordsUseCaseTests.swift index 92c5679..40fd22e 100644 --- a/Libraries/Filter/Test/Tests/Use Cases/FilterWordsUseCaseTests.swift +++ b/Libraries/Filter/Test/Tests/Use Cases/FilterWordsUseCaseTests.swift @@ -33,12 +33,12 @@ final class FilterWordsUseCaseTests: XCTestCase { // THEN XCTAssertFalse(output.isEmpty) XCTAssertEqual(output, [ - .init(word: "Eight", count: 1), - .init(word: "Five", count: 1), - .init(word: "Four", count: 1), - .init(word: "Nine", count: 1), - .init(word: "Seven", count: 1), - .init(word: "Three", count: 1), + .init(word: .init(term: "Eight", token: "eight"), count: 1), + .init(word: .init(term: "Five", token: "five"), count: 1), + .init(word: .init(term: "Four", token: "four"), count: 1), + .init(word: .init(term: "Nine", token: "nine"), count: 1), + .init(word: .init(term: "Seven", token: "seven"), count: 1), + .init(word: .init(term: "Three", token: "three"), count: 1), ]) } @@ -52,18 +52,18 @@ final class FilterWordsUseCaseTests: XCTestCase { // THEN XCTAssertFalse(output.isEmpty) XCTAssertEqual(output, [ - .init(word: "Five", count: 2), - .init(word: "Nine", count: 2), - .init(word: "Three", count: 2), - .init(word: "Eight", count: 1), - .init(word: "Four", count: 1), - .init(word: "Seven", count: 1), + .init(word: .init(term: "Five", token: "five"), count: 2), + .init(word: .init(term: "Nine", token: "nine"), count: 2), + .init(word: .init(term: "Three", token: "three"), count: 2), + .init(word: .init(term: "Eight", token: "eight"), count: 1), + .init(word: .init(term: "Four", token: "four"), count: 1), + .init(word: .init(term: "Seven", token: "seven"), count: 1), ]) } func testCallAsFunction_withInput_hasSomeRepeatedCaseSensitiveLongWords() throws { // GIVEN - input = "one two three Three four Five five six seven eight nine nine ten" + input = "one two three ThReE four FIVE five six seven eight NiNe nInE ten" // WHEN output = try sut(input) @@ -71,18 +71,18 @@ final class FilterWordsUseCaseTests: XCTestCase { // THEN XCTAssertFalse(output.isEmpty) XCTAssertEqual(output, [ - .init(word: "Five", count: 2), - .init(word: "Nine", count: 2), - .init(word: "Three", count: 2), - .init(word: "Eight", count: 1), - .init(word: "Four", count: 1), - .init(word: "Seven", count: 1), + .init(word: .init(term: "Five", token: "five"), count: 2), + .init(word: .init(term: "Nine", token: "nine"), count: 2), + .init(word: .init(term: "Three", token: "three"), count: 2), + .init(word: .init(term: "Eight", token: "eight"), count: 1), + .init(word: .init(term: "Four", token: "four"), count: 1), + .init(word: .init(term: "Seven", token: "seven"), count: 1), ]) } - func testCallAsFunction_withInput_hasSomeRepeatedDiacriticSensitiveLongWords() throws { + func testCallAsFunction_withInput_hasSomeDiacriticSensitiveLongWords() throws { // GIVEN - input = "one two thrèé Three four Fíve fïve six Šëvêń seven eight niñe nine ten" + input = "one two three Thrèé four FiVe FIVE six Šëvêń seven eight niñe nine ten" // WHEN output = try sut(input) @@ -90,12 +90,15 @@ final class FilterWordsUseCaseTests: XCTestCase { // THEN XCTAssertFalse(output.isEmpty) XCTAssertEqual(output, [ - .init(word: "Five", count: 2), - .init(word: "Nine", count: 2), - .init(word: "Seven", count: 2), - .init(word: "Three", count: 2), - .init(word: "Eight", count: 1), - .init(word: "Four", count: 1), + .init(word: .init(term: "Five", token: "five"), count: 2), + .init(word: .init(term: "Eight", token: "eight"), count: 1), + .init(word: .init(term: "Four", token: "four"), count: 1), + .init(word: .init(term: "Nine", token: "nine"), count: 1), + .init(word: .init(term: "Niñe", token: "niñe"), count: 1), + .init(word: .init(term: "Seven", token: "seven"), count: 1), + .init(word: .init(term: "Three", token: "three"), count: 1), + .init(word: .init(term: "Thrèé", token: "thrèé"), count: 1), + .init(word: .init(term: "Šëvêń", token: "šëvêń"), count: 1), ]) }