Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
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.
import UIKit
import NaturalLanguage
import PlaygroundSupport
//: Your secret API key from `` goes here.
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: "\(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
let tag = tag,
tag == .adjective
else { return nil }
return ReplacementTarget(value: String(testString[range]), range: range)
print("Found \(adjectives.count) adjective(s): \({ $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.")
//: Attempt to decode the response.
do {
let response = try decoder.decode([ThesaurusResponse].self, from: data)
let synonyms = response.first?.meta.synonyms.first,
else {
print("No synonyms returned from the API.")
//: 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.