How to use NSStringEnumerationOptions.ByWords with punctuation - swift

I'm using this code to find the NSRange and text content of the string contents of a NSTextField.
nstext.enumerateSubstringsInRange(NSMakeRange(0, nstext.length),
options: NSStringEnumerationOptions.ByWords, usingBlock: {
(substring, substringRange, _, _) -> () in
//Do something with substring and substringRange
}
The problem is that NSStringEnumerationOptions.ByWords ignores punctuation, so that
Stop clubbing, baby seals
becomes
"Stop" "clubbing" "baby" "seals"
not
"Stop" "clubbing," "baby" "seals
If all else fails I could just check the characters before or after a given word and see if they are on the exempted list (where would I find which characters .ByWords exempts?); but there must be a more elegant solution.
How can I find the NSRanges of a set of words, from a string which includes the punctuation as part of the word?

You can use componentsSeparatedByString instead
var arr = nstext.componentsSeparatedByString(" ")
Output :
"Stop" "clubbing," "baby" "seals

Inspired by Richa's answer, I used componentsSeparatedByString(" "). I had to add a bit of code to make it work for me, since I wanted the NSRanges from the output. I also wanted it to still work if there were two instances of the same word - e.g. 'please please stop clubbing, baby seals'.
Here's what I did:
var words: [String] = []
var ranges: [NSRange] = []
//nstext is a String I converted to a NSString
words = nstext.componentsSeparatedByString(" ")
//apologies for the poor naming
var nstextLessWordsWeHaveRangesFor = nstext
for word in words
{
let range:NSRange = nstextLessWordsWeHaveRangesFor.rangeOfString(word)
ranges.append(range)
//create a string the same length as word so that the 'ranges' don't change in the future (if I just replace it with "" then the future ranges will be wrong after removing the substring)
var fillerString:String = ""
for var i=0;i<word.characters.count;++i{
fillerString = fillerString.stringByAppendingString(" ")
}
nstextLessWordsWeHaveRangesFor = nstextLessWordsWeHaveRangesFor.stringByReplacingCharactersInRange(range, withString: fillerString)
}

Related

Iterate trough every word from WordsArray to take every character from it

I'm making some hangman app so words i use should be displayed with "?" instead of letters
if let wordsUrl = Bundle.main.url(forResource: "start", withExtension: "txt"){
if let wordsContent = try? String(contentsOf: wordUrl){
var allWords = wordsContent.components(separatedBy: "\n")
I don't know how to index every word from allWords array.? After that i would change letters using another property which i would use to display
for letter in word {
usedLetters.append(letter)
promptWord.append("?")
I’d recommend creating a method which you can call whenever your text field needs updating due to something such as a new letter input from the user.
var wordTextField: UITextField!
var usedWords = [] // Array to track the words already used by the user
let word = "hangman" // Word for the user to guess
var promptWord = "" // What will be displayed in the wordTextField
func updateTextField() {
for letter in word.uppercased() {
let strLetter = String(letter)
if usedLetters.contains(strLetter) {
promptWord += strLetter
} else {
promptWord += "?"
}
}
wordTextField.text = promptWord
A brief explanation of what the code does:
Firstly it iterates through the word inspecting each letter (uppercase so that there are no inconsistencies with the characters when the comparison is made to what the user has entered as their guess).
Secondly it checks to see if the strLetter is contained within the usedLetters array if it is then it places the letter inside of the correct location in the promptWord.
Whenever the letter is not found to be contained within the usedWords array a “?” is instead added to the string.
Finally the text of the wordTextField is set to be the promptWord displaying the amount of letters which the user has left to guess and how many as well as which letters the user has guessed correctly.
You can convert a String to an array of characters:
let string = "a String"
let characters = Array(characters)
So you could map your array of words to an array of arrays of characters like this:
var allWordsAsCharacterArrays = allWords.map { Array($0) }
You can also populate strings with question marks using String.init(repeating:count:)
When you pick a word from your words array, you could convert it to an array of characters, and a working string that you would populate with an array of question marks. As the user picks letters, you could replace the question marks in the working string with the correct letters from the word they are guessing.
It looks like you are just trying to provide the user ultimately with a hidden word containing only question marks. May I suggest a more straight forward approach?
let wordToGuess = "Hangman"
let hiddenWord = String(repeating: "?", count: wordToGuess.count)
now when the user guesses you can replace the proper characters
let guess = "h" // get from your user input
if wordToGuess.localizedStandardContains(guess) {
var location = 0
for c in wordToGuess {
if c.lowercased() == guess.lowercased() {
let index = hiddenWord.index(hiddenWord.startIndex, offsetBy: location)
hiddenWord = hiddenWord.replacingCharacters(in: index...index , with: String(c) )
print("hidden word now: \(hiddenWord)")
}
location += 1
}
}
note this is pretty messy code. It works, but I'm sure there is a much better way.

Swift: How to identify and delete prepositions in a string

I am trying to identify keys word in user entry to search for, so I thought of filtering out some parts of speech in order to extract key words to query in my database .
currently I use the code below to replace the word "of" from a string
let rawString = "I’m jealous of my parents. I’ll never have a kid as cool as theirs, one who is smart, has devilishly good looks, and knows all sorts of funny phrases."
var filtered = self.rawString.replacingOccurrences(of: "of", with: "")
what I want to do now is extend it to replace all preposition in a string.
What I was thinking of doing is creating a huge list of known prepositions like
let prepositions = ["in","through","after","under","beneath","before"......]
and then spliting the string by white space with
var WordList : [String] = filtered.components(separatedBy: " ")
and then looping through the wordlist to find a prepositional match and deleting it. Creating the list will be ugly and might not be efficient for my code.
What is the best way to detect and delete prepositions from a string?
Use NaturalLanguage:
import NaturalLanguage
let text = "The ripe taste of cheese improves with age."
let tagger = NLTagger(tagSchemes: [.lexicalClass])
tagger.string = text
let options: NLTagger.Options = [.omitPunctuation, .omitWhitespace]
var newSentence = [String]()
tagger.enumerateTags(in: text.startIndex..<text.endIndex, unit: .word, scheme: .lexicalClass, options: options) { tag, tokenRange in
guard let tag = tag, tag != .preposition else { return true }
newSentence.append("\(text[tokenRange])")
return true
}
print("Input: \(text)")
print("Output: \(newSentence.joined(separator: " "))")
This prints:
Input: The ripe taste of cheese improves with age.
Output: The ripe taste cheese improves age
Notice the two prepositions of and with are removed. My approach also removes the punctuation; you can adjust this with the .omitPunctuation option.
var newString = rawString
.split(separator: " ")
.filter{ !prepositions.contains(String($0))}
.joined(separator: " ")

Swift - How to get all occurrences of a range?

I'm trying to use regex and range for the first time in Swift. I want to see if the letter the user enters into the textfield will match the word that they have to guess. If it does match the matching letter or letters will be displayed in a UILabel (similar to how you play hangman, if you guess the correct letter once and there are multiple occurrences of that letter, all occurrences will show). When a button is clicked the method below is called. It works fine when finding the matching letters, and inserting them at the right location, BUT when the UILabel is updated after the loop it only updates the label with the result of the second/final loop. How can I get a combination of the result from all the iterations of the loop? Any help would be appreciated. Thank you
func findLetter(displayedWord toSearchin: String, userInput toSearchFor: String) {
let ranges: [NSRange]
var labelUpdate = String()
do {
let regex = try NSRegularExpression(pattern: toSearchFor, options: [])
let displayedWord = toSearchin as NSString
let rangeOfSearch = NSMakeRange(0, displayedWord.length)
ranges = regex.matches(in: toSearchin, range: rangeOfSearch).map {$0.range}
let nsStringlabel = wordLabel.text as NSString?
for range in ranges {
labelUpdate = (nsStringlabel?.replacingCharacters(in: range, with: toSearchFor))!
print(labelUpdate)
//the word is lavenders, so this prints:
//___e_____
//______e__
// I want:
//___e__e__
}
DispatchQueue.main.async(execute: {
self.wordLabel.text = labelUpdate
})
}
catch {
ranges = []
}
}
As stated in a comment, you’re always updating the original nsStringlabel variable, thus always overriding the previous modification in the previous loop run.
I’d recommend you init labelUpdate with wordLabel.text as NSString? and completely remove nsStringlabel. This should solve your problem.
That being said, there are a lot of other problems that could be fixed in this function.
In particular, why use regexes? It’s expensive and not useful there.
Also, you’re dispatching before setting the label value, which is good, but not before retrieving the value… either a dispatch is needed or it is not, but it cannot be needed at one place and not at the other. If you call your function from the main thread (as a response to a user input for instance), you should be good and not need a dispatch.
Here what I would have done (should be safer and faster):
func updateLabel(withDestinationWord destinationWord: String, userInput: String) {
var labelText = wordLabel.text
var startIndex = labelText.characters.startIndex
while let r = destinationWord.range(of: userInput, options: .caseInsensitive, range: startIndex..<labelText.characters.endIndex) {
labelText.replaceSubrange(r, with: userInput)
startIndex = labelText.characters.index(after: r.lowerBound)
}
wordLabel.text = labelText
}
Be sure to have the same length for wordLabel.text and destinationWord!

How to capitalize first word in every sentence with Swift

By taking into account of user locale, how can I capitalize the first word of each sentence in a paragraph? What I want to achieve is no matter the case inside the sentence, the first letter of each word will be uppercase and the rest will be lowercase. I can do only one sentence by first converting everything to lower case, then get first letter and make uppercase and finally add them up together. My question is different than How to capitalize each word in a string using Swift iOS since I don't want to capitalize each word. I just want to capitalize the first word of each sentence. capitalizedString turns
"this is first sentence. this is second sentence."
to
"This Is First Sentence. This Is Second Sentence."
What I want is
"This is first sentence. This is second sentence."
My question is also different than Capitalise first letter of every sentence Since #rintaro's code doesn't work on my below example. It keeps capital letters in original text intact. With #rintaro's code;
before
"someSentenceWith UTF text İŞğĞ. anotherSentenceğüÜğ"
after
"SomeSentenceWith UTF text İŞğĞ. AnotherSentenceğüÜğ."
What I want to achieve,
"Somesentencewith utf text işğğ. Anothersentenceğüüğ."
My code below can only do partial conversion.
var description = "someSentenceWith UTF text İŞğĞ. anotherSentenceğüÜğ"
description = description.lowercaseStringWithLocale(NSLocale.currentLocale())
let first = description.startIndex
let rest = advance(first,1)..<description.endIndex
let capitalised = description[first...first].uppercaseStringWithLocale(NSLocale.currentLocale()) + description[rest]
I will really appreciate if you can please read my question carefully, since this is the third time I am editing the question. I am really sorry if I couldn't ask it clearly since I am not a native speaker. So even though #rintaro answered similar question, his answer doesn't solve my problem. #martin-r suggests a Objective-C answer which again doesn't solve the problem I have. There were another user eric something who also suggested another answer but deleted afterwards. I just can't understand why several people suggest different answer which doesn't answer my question.
Try:
let str = "someSentenceWith UTF text İŞğĞ. anotherSentenceğüÜğ"
var result = ""
str.uppercaseString.enumerateSubstringsInRange(indices(str), options: .BySentences) { (sub, _, _, _) in
result += sub[sub.startIndex ... sub.startIndex]
result += sub[sub.startIndex.successor() ..< sub.endIndex].lowercaseString
}
println(result) // -> "Somesentencewith utf text i̇şğğ. Anothersentenceğüüğ"
ADDED: Swift2
let str = "someSentenceWith UTF text İŞğĞ. anotherSentenceğüÜğ"
var result = ""
str.uppercaseString.enumerateSubstringsInRange(str.characters.indices, options: .BySentences) { (sub, _, _, _) in
result += String(sub!.characters.prefix(1))
result += String(sub!.characters.dropFirst(1)).lowercaseString
}
print(result)
Updating #rintaro's code for Swift 3:
let str = "someSentenceWith UTF text İŞğĞ. anotherSentenceğüÜğ"
var result = ""
str.uppercased().enumerateSubstrings(in: str.startIndex..<str.endIndex, options: .bySentences) { (sub, _, _, _) in
result += String(sub!.characters.prefix(1))
result += String(sub!.characters.dropFirst(1)).lowercased()
}
print(result)
You can use Regular Expressions to achieve this. I'm adding this function as a String extension so it will be trivial to call in the future:
extension String {
func toUppercaseAtSentenceBoundary() -> String {
var string = self.lowercaseString
var capacity = string.utf16Count
var mutable = NSMutableString(capacity: capacity)
mutable.appendString(string)
var error: NSError?
if let regex = NSRegularExpression(
pattern: "(?:^|\\b\\.[ ]*)(\\p{Ll})",
options: NSRegularExpressionOptions.AnchorsMatchLines,
error: &error
) {
if let results = regex.matchesInString(
string,
options: NSMatchingOptions.allZeros,
range: NSMakeRange(0, capacity)
) as? [NSTextCheckingResult] {
for result in results {
let numRanges = result.numberOfRanges
if numRanges >= 1 {
for i in 1..<numRanges {
let range = result.rangeAtIndex(i)
let substring = mutable.substringWithRange(range)
mutable.replaceCharactersInRange(range, withString: substring.uppercaseString)
}
}
}
}
}
return mutable
}
}
var string = "someSentenceWith UTF text İŞğĞ. anotherSentenceğüÜğ.".toUppercaseAtSentenceBoundary()
I wrote this extension in Swift 3 according to #Katy's code
extension String {
func toUppercaseAtSentenceBoundary() -> String {
var result = ""
self.uppercased().enumerateSubstrings(in: self.startIndex..<self.endIndex, options: .bySentences) { (sub, _, _, _) in
result += String(sub!.characters.prefix(1))
result += String(sub!.characters.dropFirst(1)).lowercased()
}
return result as String
}
}
How to use:
let string = "This is First sentence. This is second Sentence.".toUppercaseAtSentenceBoundary()
print(string) /* Output: "This is first sentence. This is second sentence." */

Efficiently remove the last word from a string in Swift

I am trying to build an autocorrect system, so I need to be able to delete the last word typed and replace it with the correct one. My solution:
func autocorrect() {
hasWordReadyToCorrect = false
var wordProxy = self.textDocumentProxy as UITextDocumentProxy
var stringOfWords = wordProxy.documentContextBeforeInput
fullString = "Unset Value"
if stringOfWords != nil {
var words = stringOfWords.componentsSeparatedByCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
for word in words {
arrayOfWords += [word]
}
println("The last word of the array is \(arrayOfWords.last)")
for (mistake, word) in autocorrectList {
println("The mistake is \(mistake)")
if mistake == arrayOfWords.last {
fullString = word
hasWordReadyToCorrect = true
}
}
println("The corrected String is \(fullString)")
}
}
This method is called after each keystroke, and if the space is pressed, it corrects the word. My problem comes in when the string of text becomes longer than about 20 words. It takes a while for it to fill the array each time a character is pressed, and it starts to lag to a point of not being able to use it. Is there a more efficient and elegant Swift way of writing this function? I'd appreciate any help!
This doesn't answer the OP's "autocorrect" issue directly, but this is code is probably the easiest way to answer the question posed in the title:
Swift 3
let myString = "The dog jumped over a fence"
let myStringWithoutLastWord = myString.components(separatedBy: " ").dropLast().joined(separator: " ")
1.
One thing, iteration isn't necessary for this:
for word in words {
arrayOfWords += [word]
}
You can just do:
arrayOfWords += words
2.
Breaking the for loop will prevent iterating unnecessarily:
for (mistake, word) in autocorrectList {
println("The mistake is \(mistake)")
if mistake == arrayOfWords.last {
fullString = word
hasWordReadyToCorrect = true
break; // Add this to stop iterating through 'autocorrectList'
}
}
Or even better, forget the for-loop completely:
if let word = autocorrectList[arrayOfWords.last] {
fullString = word
hasWordReadyToCorrect = true
}
Ultimately what you're doing is seeing if the last word of the entered text matches any of the keys in the autocorrect list. You can just try to get the value directly using optional binding like this.
---
I'll let you know if I think of more.