Created
March 30, 2020 22:21
-
-
Save JoshuaSullivan/e5fe8cd6c77fa17988237577e58a27cc to your computer and use it in GitHub Desktop.
Uses the NaturalLanguage framework combined with a thesaurus API to replace adjectives in a sentence with synonyms. This is designed to be run in a Swift Playground.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import NaturalLanguage | |
import PlaygroundSupport | |
//: Your secret API key from `https://dictionaryapi.com` goes here. | |
//: THIS WON'T WORK UNLESS YOU GET A KEY. | |
let thesaurusKey = "" | |
//: The string you want to work on. | |
var testString = "The bright sun set behind the green hills. Thin clouds streaked the red sky." | |
//: Since we are making an API request as part of this playground, we need to allow it time to finish. | |
PlaygroundPage.current.needsIndefiniteExecution = true | |
/// The API returns an array of this data type. We're going to ignore everything except the synonyms. | |
struct ThesaurusResponse: Decodable { | |
struct Metadata: Decodable { | |
/// An array of synonym arrays. Each array is related to a different homograph of the query. Picking the right | |
/// one by context will require a deeper analysis of the API response. | |
let synonyms: [[String]] | |
enum CodingKeys: String, CodingKey { | |
case synonyms = "syns" | |
} | |
} | |
/// The top-level wrapper for metadata. | |
let meta: Metadata | |
} | |
/// Keep track of where in the string the word is we're going to replace so that we can do efficient string replacement. | |
/// Also, this will prevent all homographs identical to the query from being replaced simultaneously. | |
struct ReplacementTarget { | |
let value: String | |
let range: Range<String.Index> | |
} | |
/// Create a URLRequest for the dictionary API. | |
/// | |
/// - Parameter query: The string which we are requesting synonyms for. | |
/// - Returns: A URLRequest that can be executed to query the API. | |
/// | |
func createRequest(for query: String) -> URLRequest { | |
guard let escapedQuery = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { | |
fatalError("The query '\(query)' could not be requested.") | |
} | |
var urlComps = URLComponents(string: "https://www.dictionaryapi.com/api/v3/references/thesaurus/json/\(escapedQuery)")! | |
urlComps.queryItems = [URLQueryItem(name: "key", value: thesaurusKey)] | |
var urlRequest = URLRequest(url: urlComps.url!) | |
urlRequest.httpMethod = "GET" | |
return urlRequest | |
} | |
/// Requet sender using default configuration. | |
let session = URLSession(configuration: .default) | |
/// Response JSON parser. | |
let decoder = JSONDecoder() | |
print("Tagging the string: \(testString)") | |
//: We want to examine the entire string. | |
let stringRange = testString.startIndex..<testString.endIndex | |
//: Create and set up the NLTagger instance that will categorize the words. We are only interested in the words, | |
//: not the punctuation or white spaces. | |
let tagger = NLTagger(tagSchemes: [.nameTypeOrLexicalClass]) | |
tagger.string = testString | |
let allTags = tagger.tags( | |
in: stringRange, | |
unit: .word, | |
scheme: .nameTypeOrLexicalClass, | |
options: [.omitWhitespace, .omitPunctuation] | |
) | |
//: Filter the tags to find only the adjectives. | |
let adjectives = allTags.compactMap { (tag, range) -> ReplacementTarget? in | |
guard | |
let tag = tag, | |
tag == .adjective | |
else { return nil } | |
return ReplacementTarget(value: String(testString[range]), range: range) | |
} | |
print("Found \(adjectives.count) adjective(s): \(adjectives.map{ $0.value }.joined(separator: ", "))") | |
//: Pick a random adjective to send to the API for synonyms. | |
guard let selectedTarget = adjectives.randomElement() else { | |
fatalError("No adjectives found in string.") | |
} | |
//: Construct and send the URLRequest. | |
let query = selectedTarget.value | |
print("Executing API request for '\(query)'.") | |
let request = createRequest(for: query) | |
let task = session.dataTask(with: request) { (data, response, error) in | |
//: Whether the response is a success or failure, we want playground execution to finish once we're done here. | |
defer { PlaygroundPage.current.finishExecution() } | |
//: Ensure we received a response and not an error. | |
guard let data = data else { | |
if let error = error { | |
print("ERROR: \(error.localizedDescription)") | |
} else { | |
print("ERROR: Query failed for an unknown reason.") | |
} | |
return | |
} | |
//: Attempt to decode the response. | |
do { | |
let response = try decoder.decode([ThesaurusResponse].self, from: data) | |
guard | |
let synonyms = response.first?.meta.synonyms.first, | |
!synonyms.isEmpty | |
else { | |
print("No synonyms returned from the API.") | |
return | |
} | |
//: Until such time as we do extra work to at least ensure that we are picking the correct part-of-speech | |
//: ("light" as a verb or a noun, for instance), we're just picking the first synonym set and picking a | |
//: random replacement. | |
print("Found \(synonyms.count) synonyms: \(synonyms.joined(separator: ", "))") | |
guard let substitution = synonyms.randomElement() else { | |
fatalError("Unable to pick a substitution.") | |
} | |
print("Picked '\(substitution)' as a substitution.") | |
let modifiedString = testString.replacingCharacters(in: selectedTarget.range, with: substitution) | |
print("Modified string: \(modifiedString)") | |
} catch { | |
print("Failed to decode the API response: \(error.localizedDescription)") | |
print("\(String(data: data, encoding: .utf8)!)") | |
} | |
} | |
//: Send the request to the API. | |
task.resume() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment