[Library] Word filtering in Foundation library (#6)
This PR contains the work done to Implemented the `FilterWordsUseCase` use case in the Foundation library, which will be used to filter the content of the reviews. Reviewed-on: #6 Co-authored-by: Javier Cicchelli <javier@rock-n-code.com> Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
This commit is contained in:
parent
7c016b50d6
commit
ef6478dcc6
@ -0,0 +1,120 @@
|
|||||||
|
//
|
||||||
|
// FilterWordsUseCase.swift
|
||||||
|
// ReviewsParserKit
|
||||||
|
//
|
||||||
|
// Created by Javier Cicchelli on 17/03/2024.
|
||||||
|
// Copyright © 2024 Röck+Cöde VoF. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct FilterWordsUseCase {
|
||||||
|
|
||||||
|
// MARK: Type aliases
|
||||||
|
public typealias Input = String
|
||||||
|
public typealias Output = [Tuple]
|
||||||
|
|
||||||
|
// MARK: Initialisers
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
// MARK: Functions
|
||||||
|
public func callAsFunction(_ input: Input) throws -> Output {
|
||||||
|
let regularExpression = try NSRegularExpression(
|
||||||
|
pattern: .Pattern.wordsLongerThan4Characters,
|
||||||
|
options: .caseInsensitive
|
||||||
|
)
|
||||||
|
|
||||||
|
let matches = regularExpression.matches(
|
||||||
|
in: input,
|
||||||
|
options: [],
|
||||||
|
range: .init(input.startIndex..., in: input)
|
||||||
|
)
|
||||||
|
|
||||||
|
guard !matches.isEmpty else { return [] }
|
||||||
|
|
||||||
|
let wordsAll = matches.compactMap {
|
||||||
|
Range($0.range, in: input).map { String(input[$0]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
wordsAll
|
||||||
|
.filter { compareWords(word, $0) }
|
||||||
|
.count
|
||||||
|
}
|
||||||
|
.flatMap { (key, values) -> Output in
|
||||||
|
values.map {
|
||||||
|
.init(word: $0, count: key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sorted {
|
||||||
|
guard $0.count != $1.count else {
|
||||||
|
return $0.word < $1.word
|
||||||
|
}
|
||||||
|
|
||||||
|
return $0.count > $1.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
private extension FilterWordsUseCase {
|
||||||
|
|
||||||
|
// MARK: Functions
|
||||||
|
func compareWords(
|
||||||
|
_ lword: String,
|
||||||
|
_ rword: String
|
||||||
|
) -> Bool {
|
||||||
|
lword.compare(rword, options: [
|
||||||
|
.caseInsensitive,
|
||||||
|
.diacriticInsensitive
|
||||||
|
]) == .orderedSame
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
static let wordsLongerThan4Characters = "\\w{4,}"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
//
|
||||||
|
// FilterWordsUseCaseTests.swift
|
||||||
|
// ReviewsFoundationTest
|
||||||
|
//
|
||||||
|
// Created by Javier Cicchelli on 18/03/2024.
|
||||||
|
// Copyright © 2024 Röck+Cöde VoF. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import ReviewsFoundationKit
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
final class FilterWordsUseCaseTests: XCTestCase {
|
||||||
|
|
||||||
|
// MARK: Properties
|
||||||
|
private var input: FilterWordsUseCase.Input!
|
||||||
|
private var output: FilterWordsUseCase.Output!
|
||||||
|
private var sut: FilterWordsUseCase!
|
||||||
|
|
||||||
|
// MARK: Setup
|
||||||
|
override func setUp() async throws {
|
||||||
|
sut = .init()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Function tests
|
||||||
|
func testCallAsFunction_withInput_hasSomeLongWords() throws {
|
||||||
|
// GIVEN
|
||||||
|
input = "one two three four five six seven eight nine ten"
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
output = try sut(input)
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCallAsFunction_withInput_hasSomeRepeatedLongWords() throws {
|
||||||
|
// GIVEN
|
||||||
|
input = "one two three three four five five six seven eight nine nine ten"
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
output = try sut(input)
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCallAsFunction_withInput_hasSomeRepeatedCaseSensitiveLongWords() throws {
|
||||||
|
// GIVEN
|
||||||
|
input = "one two three Three four Five five six seven eight nine nine ten"
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
output = try sut(input)
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCallAsFunction_withInput_hasSomeRepeatedDiacriticSensitiveLongWords() throws {
|
||||||
|
// GIVEN
|
||||||
|
input = "one two thrèé Three four Fíve fïve six Šëvêń seven eight niñe nine ten"
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
output = try sut(input)
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCallAsFunction_withInput_hasOnlyShortWords() throws {
|
||||||
|
// GIVEN
|
||||||
|
input = "no one is a two"
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
output = try sut(input)
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
XCTAssertTrue(output.isEmpty)
|
||||||
|
XCTAssertEqual(output, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCallAsFunction_withEmptyInput() throws {
|
||||||
|
// GIVEN
|
||||||
|
input = .empty
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
output = try sut(input)
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
XCTAssertTrue(output.isEmpty)
|
||||||
|
XCTAssertEqual(output, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user