Iterate over part of String in Swift - swift

Why in the world are Swift String operations so complex and tiresome to work with?
I have to iterate over a String in reverse but ignoring the first char. Now this could be done like following:
var firstTime = true
for i in textBefore.characters.reversed() {
if firstTime {
firstTime = false
} else {
if String(i).personalFunction() {
// something
} else {
// something else
}
}
}
But really I just want to do something like:
textBefore = textBefore.characters.reversed()
for i in 1...textBefore.characters.count {
if textBefore.get(i).personalFunction() {
// something
} else {
// something else
}
}
So why can't we get index as int. And why is textBefore.characters.reversed() not a String or simply have String have a reverse function. All these issues just makes it so frustrating to work with Strings in Swift and makes us do stupid stuff as converting a String to an array of chars :S or stuff like my proposed solution above... Also we can't make for loops in the old fashion... I simply need some Swift guru to point my brain in the right direction for this stuff.

string.characters is a collection of characters.
Use reversed() to access the elements in reverse order, anddropFirst() to skip the initial element of the reversed collection:
let string = "a🇨🇷b😈"
for ch in string.characters.reversed().dropFirst() {
print(ch)
// `ch` is a Character. Use `String(ch)` if you need a String.
}
Output:
b
🇨🇷
a

You can do something like your second one. After you enter the for, you can just get the index directly from the string. In Swift, a string is just an array of characters.
textBefore = String(textBefore.characters.reversed())
for i in 1...textBefore.characters.count {
if textBefore[i].personalFunction() {
// something
} else {
// something else
}
}

Related

How to fix deprecated substring(with: )

I'm working through a book exercise where the author has the following code.
func getMatchingString(str: String) -> String? {
if let newMatch = str.range(of:regExFindMatchString,
options:.regularExpression) {
return str.substring(with: newMatch)
} else {
return nil
}
}
the str.substring(with: newMatch) line shows 'substring(with:) is deprecated use String slicing subscript.'
I've tried to figure out what to use to fix this but everything I've tried shows I just don't understand this well enough to fix it. I know that the newMatch is a range object, just can't figure out how to use it effectively.
Any help is greatly appreciated..
Bob
Just use subscripting with the range you obtain from the if let
if let newMatchRange = str.range(of:regExFindMatchString, options:.regularExpression) {
return String(str[newMatchRange])
} else {
return nil
}
You need to translate it to a String as the method actually returns a Substring type, not a String.

Remove specific characters from a String using an extension

I’ve found this awesome extension that remove everything except the characters in the quotation marks.
extension String.UnicodeScalarView {
var removeCharacters: String {
return String(filter(("cfh".unicodeScalars).contains))
}
}
print("abcd123efg".unicodeScalars.removeCharacters) // it prints “cf”, my desirable result is to make it print “abd123eg”
It prints “cf”, my desirable result is to make it print “abd123eg”.
Can you help me invert it to remove only the characters that are between the quotation marks and leave everything else?
Note:It is also important that it recognize (unicodeScalars) so please don’t remove this part.
You need to negate the call to contains:
return String(filter { !"cfh".unicodeScalars.contains($0) })
Taking #rmaddy into consideration, if you are using extension then make sure you think about generics and work in any case. Like you have kept "cfh" as a static String, what if you want to remove "abc", then it won't work. So, here is the modified version:
extension String.UnicodeScalarView {
func removeCharacters(_ characters: String) -> String {
return String(filter { !characters.unicodeScalars.contains($0) })
}
}
Usage: "abcd123efg".unicodeScalars.removeCharacters("cfh")
Create an extension for String like below
extension String {
func removeGroupOfCharacters(removalString : String) -> String {
// return an Array without the removal Characters
let filteredCharacters = Array(self).filter { !Array(removalString).contains($0) }
// build a String with the filtered Array
let filtered = String(filteredCharacters)
return filtered
}
}
override func viewDidLoad() {
super.viewDidLoad()
let unfiltered = "abcd123efg"
let removal = "cfh"
print(unfiltered.removeGroupOfCharacters(removalString: removal))
// => output will be like this "abd123eg\n"
}
It's a basic example. You can do like that.
extension String {
return aString.replacingOccurrences(of: "X", with: "")
}

Swift `reversed()` not ordering how I thought it would

Why would this produce Elena instead of Paula?
let names = ["Paula", "Elena", "Zoe"]
var lastNameEndingInA: String?
for name in names.reversed() where name.hasSuffix("a") {
lastNameEndingInA = name
break
}
lastNameEndingInA // Optional("Elena")”
I would have thought that names.reversed() would be [“Zoe”, “Paula”, “Elena”] and then where name.hasSuffix(“a”) would skip “Zoe” and then go to “Paula” then break so lastNameEndingInA would be “Paula”?
I can’t figure out why it isn’t working like that though.
Your array is ["Paula", "Elena", "Zoe"]. When reversed it is ["Zoe", "Elena", "Paula"]. There is no reason to think it would be [“Zoe”, “Paula”, “Elena”]. So your code is working as expected.
But it can be written more easily as:
let lastNameEndingInA = names.reversed().first { $0.hasSuffix("a") }
It you want the names sorted in reverse and then find the match, do:
let lastNameEndingInA = names.sorted().reversed().first { $0.hasSuffix("a") }

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.

What's a good way to iterate backwards through the Characters of a String?

What's the most Swiftian way to iterate backwards through the Characters in a String? i.e. like for ch in str, only in reverse?
I think I must be missing something obvious, because the best I could come up with just now was:
for var index = str.endIndex;
index != str.startIndex;
index = index.predecessor() {
let ch = str[index.predecessor()]
...
}
I realise "what's the best..." may be classed as subjective; I suppose what I'm really looking for is a terse yet readable way of doing this.
Edit: While reverse() works and is terse, it looks like this might be quite inefficient compared to the above, i.e. it seems like it's not actually iterating backwards, but creating a full reverse copy of the characters in the String. This would be much worse than my original if, say, you were looking for something that was usually a few characters from the end of a 10,000-character String. I'm therefore leaving this question open for a bit to attract other approaches.
The reversed function reverses a C: CollectionType and returns a ReversedCollection:
for char in "string".characters.reversed() {
// ...
}
If you find that reversed pre-reverses the string, try:
for char in "string".characters.lazy.reversed() {
// ...
}
lazy returns a lazily evaluated sequence (LazyBidirectionalCollection) then reversed() returns another LazyBidirectionalCollection that is visited in reverse.
As of December 2015 with Swift version 2.1, the proper way to do this is
for char in string.characters.reverse() {
//loop backwards
}
String no longer conforms to SequenceType<T> but its character set does.
Not sure about efficiency, but I will suggest
for ch in reverse(str) {
println(ch)
}
Here is a code for reversing a string that doesn't use reverse(str)
// Reverse String
func myReverse(str:String) -> String {
var buffer = ""
for character in str {
buffer.insert(character, atIndex: buffer.startIndex)
}
return buffer
}
myReverse("Paul") // gives “luaP”
Just a little experiment. For what its worth.
Ok, leant how to read the question....
Would this work Matt?
func ReverseIteration(str:String) {
func myReverse(str:String) -> String {
var buffer = ""
for character in str {
buffer.insert(character, atIndex: buffer.startIndex)
}
return buffer
}
// reverse string then iterate forward.
var newStr = myReverse(str)
for char in newStr {
println(char)
// do some code here
}
this?
extension String {
var reverse: String {
var reverseStr = ""
for character in self {
reverseStr = String(character) + reverseStr
}
return reverseStr
}
}