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

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
}
}

Related

Count number of characters between two specific characters

Trying to make a func that will count characters in between two specified char like:
count char between "#" and "." or "#" and ".com"
If this is only solution could this code be written in a simple way with .count or something less confusing
func validateEmail(_ str: String) -> Bool {
let range = 0..<str.count
var numAt = Int()
numDot = Int()
if str.contains("#") && str.contains(".") && str.characters.first != "#" {
for num in range {
if str[str.index(str.startIndex, offsetBy: num)] == "#" {
numAt = num
print("The position of # is \(numAt)")
} else if
str[str.index(str.startIndex, offsetBy: num)] == "." {
numDot = num
print("The position of . is \(numDot)")
}
}
if (numDot - numAt) > 1 {
return true
}
}
return false
}
With help from #Βασίλης Δ. i made a direct if statement for func validateEmail that check if number of char in between are less than 1
if (str.split(separator: "#").last?.split(separator: ".").first!.count)! < 1{
return false
}
It could be usefull
There are many edge cases to what you're trying to do, and email validation is notoriously complicated. I recommend doing as little of it as possible. Many, many things are legal email addresses. So you will need to think carefully about what you want to test. That said, this addresses what you've asked for, which is the distance between the first # and the first . that follows it.
func lengthOfFirstComponentAfterAt(in string: String) -> Int? {
guard
// Find the first # in the string
let firstAt = string.firstIndex(of: "#"),
// Find the first "." after that
let firstDotAfterAt = string[firstAt...].firstIndex(of: ".")
else {
return nil
}
// Return the distance between them (not counting the dot itself)
return string.distance(from: firstAt, to: firstDotAfterAt) - 1
}
lengthOfFirstComponentAfterAt(in: "rob#example.org") // Optional(7)
There's a very important lesson about Collections in this code. Notice the expression:
string[firstAt...].firstIndex(of: ".")
When you subscript a Collection, each element of the resulting slice has the same index as in the original collection. The returned value from firstIndex can be used directly to subscript string without offsetting. This is very different than how indexes work in many other languages, and allows powerful algorithms, and also creates at lot of bugs when developers forget this.

Count the number of lines in a Swift String

After reading a medium sized file (about 500kByte) from a web-service I have a regular Swift String (lines) originally encoded in .isolatin1. Before actually splitting it I would like to count the number of lines (quickly) in order to be able to initialise a progress bar.
What is the best Swift idiom to achieve this?
I came up with the following:
let linesCount = lines.reduce(into: 0) { (count, letter) in
if letter == "\r\n" {
count += 1
}
}
This does not look too bad but I am asking myself if there is a shorter/faster way to do it. The characters property provides access to a sequence of Unicode graphemes which treat \r\n as only one entity. Checking this with all CharacterSet.newlines does not work, since CharacterSet is not a set of Character but a set of Unicode.Scalar (a little counter-intuitively in my book) which is a set of code points (where \r\n counts as two code points), not graphemes. Trying
var lines = "Hello, playground\r\nhere too\r\nGalahad\r\n"
lines.unicodeScalars.reduce(into: 0) { (cnt, letter) in
if CharacterSet.newlines.contains(letter) {
cnt += 1
}
}
will count to 6 instead of 3. So this is more general than the above method, but it will not work correctly for CRLF line endings.
Is there a way to allow for more line ending conventions (as in CharacterSet.newlines) that still achieves the correct result for CRLF? Can the number of lines be computed with less code (while still remaining readable)?
If it's ok for you to use a Foundation method on an NSString, I suggest using
enumerateLines(_ block: #escaping (String, UnsafeMutablePointer<ObjCBool>) -> Void)
Here's an example:
import Foundation
let base = "Hello, playground\r\nhere too\r\nGalahad\r\n"
let ns = base as NSString
ns.enumerateLines { (str, _) in
print(str)
}
It separates the lines properly, taking into account all linefeed types, such as "\r\n", "\n", etc:
Hello, playground
here too
Galahad
In my example I print the lines but it's trivial to count them instead, as you need to - my version is just for the demonstration.
As I did not find a generic way to count newlines I ended up just solving my problem by iterating through all the characters using
let linesCount = text.reduce(into: 0) { (count, letter) in
if letter == "\r\n" { // This treats CRLF as one "letter", contrary to UnicodeScalars
count += 1
}
}
I was sure this would be a lot faster than enumerating lines for just counting, but I resolved to eventually do the measurement. Today I finally got to it and found ... that I could not have been more wrong.
A 10000 line string counted lines as above in about 1.0 seconds , but counting through enumeration using
var enumCount = 0
text.enumerateLines { (str, _) in
enumCount += 1
}
only took around 0.8 seconds and was consistently faster by a little more than 20%. I do not know what tricks the Swift engineers hide in their sleves, but they sure manage to enumerateLines very quickly. This just for the record.
You can use the following extension
extension String {
var numberOfLines: Int {
return self.components(separatedBy: "\n").count
}
}
Swift 5 Extension
extension String {
func numberOfLines() -> Int {
return self.numberOfOccurrencesOf(string: "\n") + 1
}
func numberOfOccurrencesOf(string: String) -> Int {
return self.components(separatedBy:string).count - 1
}
}
Example:
let testString = "First line\nSecond line\nThird line"
let numberOfLines = testString.numberOfLines() // returns 3
I use this, a CharacterSet which Apple provides, made for this task:
let newLines = text.components(separatedBy: .newlines).count - 1

Iterate over part of String in 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
}
}

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 the best way to convert String into [Character] in Swift?

I would like to run a filter on a string. My first attempt failed as string is not automagically converted to Character[].
var s: String = "abc"
s.filter { $0 != "b" }
If I clumsily convert the String to Character[] with following code, it works as expected. But surely there has to be a neater way?
var cs:Character[] = []
for c in s {
cs = cs + [c]
}
cs = cs.filter { $0 != "b" }
println(cs)
String conforms to the CollectionType protocol, so you can pass it directly to the function forms of map and filter without converting it at all:
let cs = filter(s) { $0 != "f" }
cs here is an Array of Characters. You can turn it into a String by using the String(seq:) initializer, which constructs a String from any SequenceType of Characters. (SequenceType is a protocol that all lists conform to; for loops use them, among many other things.)
let filteredString = String(seq: cs)
Of course, you can just as easily put those two things in one statement:
let filteredString = String(seq: filter(s) { $0 != "f" })
Or, if you want to make a convenience filter method like the one on Array, you can use an extension:
extension String {
func filter(includeElement: Character -> Bool) -> String {
return String(seq: Swift.filter(self, includeElement))
}
}
(You write it "Swift.filter" so the compiler doesn't think you're trying to recursively call the filter method you're currently writing.)
As long as we're hiding how the filtering is performed, we might as well use a lazy filter, which should avoid constructing the temporary array at all:
extension String {
func filter(includeElement: Character -> Bool) -> String {
return String(seq: lazy(self).filter(includeElement))
}
}
I don't know of a built in way to do it, but you could write your own filter method for String:
extension String {
func filter(f: (Character) -> Bool) -> String {
var ret = ""
for character in self {
if (f(character)) {
ret += character
}
}
return ret
}
}
If you don't want to use an extension you could do this:
Array(s).filter({ $0 != "b" }).reduce("", combine: +)
You can use this syntax:
var chars = Character[]("abc")
I'm not 100% sure if the result is an array of Characters or not but works for my use case.
var str = "abc"
var chars = Character[](str)
var result = chars.map { char in "char is \(char)" }
result
The easiest way to convert a char to string is using the backslash (), for example I have a function to reverse a string, like so.
var identityNumber:String = id
for char in identityNumber{
reversedString = "\(char)" + reversedString
}