String Indexes in swift - swift

let greeting = "Hello World"
greeting[greeting.startIndex]
This code will print first character of string "H"
let = "Hello World"
greeting.startIndex
greeting.endIndex
whats the difference? this thing don't do anything. Even i dont get error for
greeting.endIndex
of course I am not retrieving string value through subscript syntax but in the Substring topic I found
greeting.endIndex
it is print only
string.index
Ok let me explain why I am asking about string.index and greeting.endIndex
let greeting = "Hello_world!"
let index = greeting.index(of: "_") ?? greeting.endIndex
let beginning = greeting[..<index]
// beginning is "Hello"
This code is related to substring, I totally understand what is it doing and in second line of code there is Nil-Coalescing Operator and you know what is it for. But what if
let greeting = "Hello world!"
let index = greeting.index(of: "_") ?? greeting.endIndex
let beginning = greeting[..<index]
// beginning is "Hello world"
If there is no "_" in string value means greeting.index(of: "_") is nil then it should be returns default value greeting.endIndex as Nil-Coalescing Operator does right. So why does greeting.endIndex returning `"Hello world!"

Your code does nothing. This code however
let greeting = "Hello"
print(greeting[greeting.startIndex]) // Prints 'H'
print(greeting[greeting.endIndex])
Causes a fatal error because greeting.endIndex actually "points" to just after the end of the String, so the last statement is an array bounds violation.

In case you describe startIndex return position of the first character in a nonempty string and the endIndex return the position one greater than the last valid subscript argument.
Also in version 4 of the Swift. String is represented as Collection.
So when you do:
greeting[greeting.startIndex]
You ask greeting string to return element in the first position that is "H"

The startIndex is the start of the string. The endIndex is the end of the string. You can use these when building ranges or when creating new indexes as an offsetBy from one or both of these.
So, for example, if you want a Substring that excluded the first and last characters, you could set fromIndex to be startIndex plus 1 and toIndex to be endIndex less 1, yielding:
let string = "Hello World"
let fromIndex = string.index(string.startIndex, offsetBy: 1)
let toIndex = string.index(string.endIndex, offsetBy: -1)
let substring = string[fromIndex..<toIndex] // "ello Worl"
You then ask:
If there is no "_" in string value means greeting.index(of: "_") is nil then it should be returns default value greeting.endIndex as Nil-Coalescing Operator does right. So why does greeting.endIndex returning `"Hello world!"
Make sure you don't conflate the string[index] syntax (which returns a Character) and the string[..<index] which returns a Substring from the startIndex up to index (i.e. "return Substring of everything up to index"):
let beginning = greeting[..<index]
This partially bound range is just short-hand for:
let beginning = greeting[greeting.startIndex..<index]
So, when you default index to the greeting.endIndex, that's like saying you want a substring that is, effectively, the whole string:
let beginning = greeting[greeting.startIndex..<greeting.endIndex]
So, that's why the syntax presented in your question, greeting[..<index], returns the whole string if _ was not found and it used endIndex.
As an aside, if you wanted different behavior, namely for it to return an empty substring if the _ is not found, you could do the following:
let index = greeting.index(of: "_") ?? greeting.startIndex
let beginning = greeting[..<index]
Or, if you think that's a little too cute, just adopt standard safe unwrapping patterns, e.g.:
guard let index = greeting.index(of: "_") else {
// handle unfound _ here
}
let beginning = greeting[..<index]

Related

Explanation of lastIndex of: and firstIndex of: used in a string in Swift

I am solving a programming problem in Swift and I found a solution online which I don't totally understand, the problem is: Write a function that reverses characters in (possibly nested) parentheses in the input string. the solution is
var inputString = "foo(bar)baz(ga)kjh"
var s = inputString
while let openIdx = s.lastIndex(of: "(") {
let closeIdx = s[openIdx...].firstIndex(of:")")!
s.replaceSubrange(openIdx...closeIdx, with: s[s.index(after: openIdx)..<closeIdx].reversed())
}
print (s) // output: foorabbazagkjh (the letters inside the braces are reversed)
I d like to have details about: lastIndex(of: does in this case
and what let closeIdx = s[openIdx...].firstIndex(of:")")! does as well
The best place to experiment with these kinds of questions would Playground. Also, check out the documentation.
Now let go through each of the statement:
let openIdx = s.lastIndex(of: "(") // it will find the last index of "(", the return type here is Array.Index?
so if I print the value after with index including till end of string, it would be
print(s[openIdx!...]) // `!` exclamation is used for forced casting
// (ga)kjh
Now for your second question;
let closeIdx = s[openIdx...].firstIndex(of:")")!
Let break it down s[openIdx...] is equal to (ga)kjh in first iteration and so it will return the index of ) after a.
The suggestion would be always break the statement and learn what each expression is doing.

How to get SubString From Range in iOS Swift 4.2 [duplicate]

This question already has answers here:
Get nth character of a string in Swift
(47 answers)
Closed 3 years ago.
I am new to swift, I want to get substring from specified range. But I am getting some errors.
I have found similar questions on getting subString from range, but those couldn't work for me. I am using XCode 10.1 and swift 4.2. I have getting error while getting substring from specified range. I have tried like this (Sample Code)
let textStr = "Sample text here"
let newRange = NSMakeRange(4,9)
let startIndex = textStr?.index((textStr?.startIndex)!, offsetBy: newRange.location)
let endIndex = textStr?.index((textStr?.startIndex)!, offsetBy: newRange.length)
let newHashtagRange = startIndex..<endIndex
let newHashTagFound = textStr[newHashtagRange]
Error:
I am getting below error for this line of code
let newHashtagRange = startIndex..<endIndex
Binary operator '..<' cannot be applied to two 'String.Index?'
operands
I just struct here from last two days.
But in Objective-C just need one line of code like SubStringFromRange(range).
You are getting an error because both startIndex and endIndex are optionals due to the optional chain you have when defining them.
That is odd because, in your example code, textStr is not actually optional and the optional chain wouldn't even compile. My guess is you were attempting to shorten the example. I will assume textStr is meant to be optional and show you how to avoid using forced unwrapping which is almost always a bad idea. Also, you are getting the end index incorrectly. You need to do an offset from the calculated start index with the length. That leaves you with this:
let textStr: String? = "Sample text here"
if let textStr = textStr {
let newRange = NSMakeRange(4,9)
let startIndex = textStr.index(textStr.startIndex, offsetBy: newRange.location)
let endIndex = textStr.index(startIndex, offsetBy: newRange.length)
let newHashtagRange = startIndex..<endIndex
let newHashTagFound = textStr[newHashtagRange]
}
However, a Range can be initialized from an NSRange which is reliable and built in (it returns an optional in case the range is out of bounds):
let textStr: String? = "Sample text here"
let newRange = NSMakeRange(4,9)
if let textStr = textStr
, let newHashtagRange = Range(newRange, in: textStr)
{
let newHashTagFound = textStr[newHashtagRange]
}

Swift 4 Substring Crash

I'm a little confused about the best practices for Swift 4 string manipulation.
How do you handle the following:
let str = "test"
let start = str.index(str.startIndex, offsetBy: 7)
Thread 1: Fatal error: cannot increment beyond endIndex
Imagine that you do not know the length of the variable 'str' above. And since 'start' is not an optional value, what is the best practice to prevent that crash?
If you use the variation with limitedBy parameter, that will return an optional value:
if let start = str.index(str.startIndex, offsetBy: 7, limitedBy: str.endIndex) {
...
}
That will gracefully detect whether the offset moves the index past the endIndex. Obviously, handle this optional however best in your scenario (if let, guard let, nil coalescing operator, etc.).
Your code doesn't do any range checking:
let str = "test"
let start = str.index(str.startIndex, offsetBy: 7)
Write a function that tests the length of the string first. In fact, you could create an extension on String that lets you use integer subscripts, and returns a Character?:
extension String {
//Allow string[Int] subscripting. WARNING: Slow O(n) performance
subscript(index: Int) -> Character? {
guard index < self.count else { return nil }
return self[self.index(self.startIndex, offsetBy: index)]
}
}
This code:
var str = "test"
print("str[7] = \"\(str[7])\"")
Would display:
str[7] = "nil"
##EDIT:
Be aware, as Alexander pointed out in a comment below, that the subscript extension above has up to O(n) performance (it takes longer and longer as the index value goes up, up to the length of the string.)
If you need to loop through all the characters in a string code like this:
for i in str.count { doSomething(string: str[i]) }
would have O(n^2) (Or n-squared) performance, which is really, really bad. in that case, you should instead first convert the string to an array of characters:
let chars = Array(str.characters)
for i in chars.count { doSomething(string: chars[i]) }
or
for aChar in chars { //do something with aChar }
With that code you pay the O(n) time cost of converting the string to an array of characters once, and then you can do operations on the array of characters with maximum speed. The downside of that approach is that it would more than double the memory requirements.

Get the string up to a specific character

var hello = "hello, how are you?"
var hello2 = "hello, how are you #tom?"
i want to delete every letter behind the # sign.
result should be
var hello2 = "hello, how are you #tom?"
->
hello2.trimmed()
print(hello2.trimmed())
-> "hello, how are you"
Update
As i want to use it to link multiple users and replace the space behind #sign with the correct name, I always need the reference to the latest occurrence of the #sign to replace it.
text3 = "hey i love you #Tom #Marcus #Peter"
Example what the final version should look like
to start off
var text = "hello #tom #mark #mathias"
i want to always get the index of the latest # sign in the text
Expanding on #appzYourLife answer, the following will also trim off the whitespace characters after removing everything after the # symbol.
import Foundation
var str = "hello, how are you #tom"
if str.contains("#") {
let endIndex = str.range(of: "#")!.lowerBound
str = str.substring(to: endIndex).trimmingCharacters(in: .whitespacesAndNewlines)
}
print(str) // Output - "hello, how are you"
UPDATE:
In response to finding the last occurance of the # symbol in the string and removing it, here is how I would approach it:
var str = "hello, how are you #tom #tim?"
if str.contains("#") {
//Reverse the string
var reversedStr = String(str.characters.reversed())
//Find the first (last) occurance of #
let endIndex = reversedStr.range(of: "#")!.upperBound
//Get the string up to and after the # symbol
let newStr = reversedStr.substring(from: endIndex).trimmingCharacters(in: .whitespacesAndNewlines)
//Store the new string over the original
str = String(newStr.characters.reversed())
//str = "hello, how are you #tom"
}
Or looking at #appzYourLife answer use range(of:options:range:locale:) instead of literally reversing the characters
var str = "hello, how are you #tom #tim?"
if str.contains("#") {
//Find the last occurrence of #
let endIndex = str.range(of: "#", options: .backwards, range: nil, locale: nil)!.lowerBound
//Get the string up to and after the # symbol
let newStr = str.substring(from: endIndex).trimmingCharacters(in: .whitespacesAndNewlines)
//Store the new string over the original
str = newStr
//str = "hello, how are you #tom"
}
As an added bonus, here is how I would approach removing every # starting with the last and working forward:
var str = "hello, how are you #tom and #tim?"
if str.contains("#") {
while str.contains("#") {
//Reverse the string
var reversedStr = String(str.characters.reversed())
//Find the first (last) occurance of #
let endIndex = reversedStr.range(of: "#")!.upperBound
//Get the string up to and after the # symbol
let newStr = reversedStr.substring(from: endIndex).trimmingCharacters(in: .whitespacesAndNewlines)
//Store the new string over the original
str = String(newStr.characters.reversed())
}
//after while loop, str = "hello, how are you"
}
let text = "hello, how are you #tom?"
let trimSpot = text.index(of: "#") ?? text.endIndex
let trimmed = text[..<trimSpot]
Since a string is a collection of Character type, it can be accessed as such. The second line finds the index of the # sign and assigns its value to trimSpot, but if it is not there, the endIndex of the string is assigned through the use of the nil coalescing operator
??
The string, or collection of Characters, can be provided a range that will tell it what characters to get. The expression inside of the brackets,
..<trimSpot
is a range from 0 to trimSpot-1. So,
text[..<trimSpot]
returns an instance of type Substring, which points at the original String instance.
You need to find the range of the "#" and then use it to create a substring up to the index before.
import Foundation
let text = "hello, how are you #tom?"
if let range = text.range(of: "#") {
let result = text.substring(to: range.lowerBound)
print(result) // "hello, how are you "
}
Considerations
Please note that, following the logic you described and using the input text you provided, the output string will have a blank space as last character
Also note that if multiple # are presente in the input text, then the first occurrence will be used.
Last index [Update]
I am adding this new section to answer the question you posted in the comments.
If you have a text like this
let text = "hello #tom #mark #mathias"
and you want the index of the last occurrency of "#" you can write
if let index = text.range(of: "#", options: .backwards)?.lowerBound {
print(index)
}
Try regular expressions, they are much safer (if you know what you are doing...)
let hello2 = "hello, how are you #tom, #my #next #victim?"
let deletedStringsAfterAtSign = hello2.replacingOccurrences(of: "#\\w+", with: "", options: .regularExpression, range: nil)
print(deletedStringsAfterAtSign)
//prints "hello, how are you , ?"
And this code removes exactly what you need and leaves the characters after the strings clear, so you can see the , and ? still being there. :)
EDIT: what you asked in comments to this answer:
let hello2 = "hello, how are you #tom, #my #next #victim?"
if let elementIwannaAfterEveryAtSign = hello2.components(separatedBy: " #").last
{
let deletedStringsAfterAtSign = hello2.replacingOccurrences(of: "#\\w+", with: elementIwannaAfterEveryAtSign, options: .regularExpression, range: nil)
print(deletedStringsAfterAtSign)
//prints hello, how are you victim?, victim? victim? victim??
}

trimmingCharacters not working as expected when characters include hyphen Swift

Trying to understand what is going wrong in playgrounds with the next example :
let result = "+-----+".trimmingCharacters(in: CharacterSet(charactersIn: "+").inverted)
result is "+-----+"
expected result is "++"
due to method reference "Returns a new string made by removing from both ends of the String characters contained in a given character set."
Examples that work how I expect:
let result = "D123ABC".trimmingCharacters(in: CharacterSet(charactersIn: "01234567890.").inverted)
result is "123"
let result = "+-----+".trimmingCharacters(in: CharacterSet(charactersIn: "*").inverted)
result is ""
trimmingCharacters only replaces the trailing/leading characters.
If you want to replace all characters that are not "+" you can use
"+-----+".replacingOccurrences(of: "[^+]", with: "", options: .regularExpression)
Agree with rmaddy.
For more explanation check this:
let result = "123+--+abc".trimmingCharacters(in: CharacterSet(charactersIn: "+").inverted)
Result: +--+
let result = "+--+".trimmingCharacters(in: CharacterSet(charactersIn: "+").inverted)
Result: +--+