Inserting a character in String (Swift) - swift

I have a String "000". I want to change this to "0.00".
I took a look at the insert function.
var str = "000"
str.insert(".", at: str.endIndex)
How do I get the index of 2 before the end index?
I tried:
str.insert(".", at: str.endIndex - 1)
but this didn't work at all.

You could also use Strings character property. Its basically an array made up of all the characters (duh) in the String.
So you would:
var str = "000"
let index = str.characters.index(str.characters.startIndex, offsetBy: 1) //here you define a place (index) to insert at
str.characters.insert(".", at: index) //and here you insert
Unfortunately you have to create an index first, as .insert does not allow you to specify the position using an Int.

Since Swift 2, String does no longer conform to SequenceType. In other words, you can not iterate through a string with a for...in loop.
The simple and easy way is to convert String to Array to get the benefit of the index just like that:
let input = Array(str)
I remember when I tried to index into String without using any conversion. I was really frustrated that I couldn’t come up with or reach a desired result, and was about to give up.
But I ended up creating my own workaround solution, and here is the full code of the extension:
extension String {
subscript (_ index: Int) -> String {
get {
String(self[self.index(startIndex, offsetBy: index)])
}
set {
if index >= count {
insert(Character(newValue), at: self.index(self.startIndex, offsetBy: count))
} else {
insert(Character(newValue), at: self.index(self.startIndex, offsetBy: index))
}
}
}
}
Now that you can read and write a single character from string using its index just like you originally wanted to:
var str = "car"
car[3] = "d"
print(str)
It’s simple and useful way to use it and get through Swift’s String access model.
Now that you’ll feel it’s smooth sailing next time when you can loop through the string just as it is, not casting it into Array.
Try it out, and see if it can help!

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 the range of the first line in a string?

I would like to change the formatting of the first line of text in an NSTextView (give it a different font size and weight to make it look like a headline). Therefore, I need the range of the first line. One way to go is this:
guard let firstLineString = textView.string.components(separatedBy: .newlines).first else {
return
}
let range = NSRange(location: 0, length: firstLineString.count)
However, I might be working with quite long texts so it appears to be inefficient to first split the entire string into line components when all I need is the first line component. Thus, it seems to make sense to use the firstIndex(where:) method:
let firstNewLineIndex = textView.string.firstIndex { character -> Bool in
return CharacterSet.newlines.contains(character)
}
// Then: Create an NSRange from 0 up to firstNewLineIndex.
This doesn't work and I get an error:
Cannot convert value of type '(Unicode.Scalar) -> Bool' to expected argument type 'Character'
because the contains method accepts not a Character but a Unicode.Scalar as a parameter (which doesn't really make sense to me because then it should be called a UnicodeScalarSet and not a CharacterSet, but nevermind...).
My question is:
How can I implement this in an efficient way, without first slicing the whole string?
(It doesn't necessarily have to use the firstIndex(where:) method, but appears to be the way to go.)
A String.Index range for the first line in string can be obtained with
let range = string.lineRange(for: ..<string.startIndex)
If you need that as an NSRange then
let nsRange = NSRange(range, in: string)
does the trick.
You can use rangeOfCharacter, which returns the Range<String.Index> of the first character from a set in your string:
extension StringProtocol where Index == String.Index {
var partialRangeOfFirstLine: PartialRangeUpTo<String.Index> {
return ..<(rangeOfCharacter(from: .newlines)?.lowerBound ?? endIndex)
}
var rangeOfFirstLine: Range<Index> {
return startIndex..<partialRangeOfFirstLine.upperBound
}
var firstLine: SubSequence {
return self[partialRangeOfFirstLine]
}
}
You can use it like so:
var str = """
some string
with new lines
"""
var attributedString = NSMutableAttributedString(string: str)
let firstLine = NSAttributedString(string: String(str.firstLine))
// change firstLine as you wish
let range = NSRange(str.rangeOfFirstLine, in: str)
attributedString.replaceCharacters(in: range, with: firstLine)

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.

How to process every two-character substrings of a String in Swift 2.2+?

C-style for loops are not supported anymore, so how would this old hex-decoder look like in Swift 2.2+?
let data = NSMutableData(capacity: trimmedString.characters.count / 2)
for var index = trimmedString.startIndex; index < trimmedString.endIndex; index = index.successor().successor() {
let byteString = trimmedString.substringWithRange(Range<String.Index>(start: index, end: index.successor().successor()))
let num = UInt8(byteString.withCString { strtoul($0, nil, 16) })
data!.appendBytes([num] as [UInt8], length: 1)
}
Use stride:
for index in 0.stride(through: trimmedString.characters.count, by: 2) {
// ...
}
To create the Range from the index, use the startIndex of the string and advance it. Example:
trimmedString.startIndex.advancedBy(index)
trimmedString.startIndex.advancedBy(index).successor().successor()
etc. Just check to make sure you aren't going out of bounds with successor().
Most loops like this can be implemented with the stride method, but String.CharacterView.Index is not Strideable. You could use the enumerate method to create an integer-based index alongside the string indices, and then use a where clause to only execute the loop every second time:
for (i, index) in trimmedString.characters.indices.enumerate() where number % 2 == 0 {
print(trimmedString.characters[i])
}

Positions of a Character in a String with Swift 2

I'm making a string extension for finding multiple positions a character can occur in a string. This is my code:
let letter: Character = "l"
extension String {
func checkLetter(letter: Character) {
if let index = self.rangeOfString(AString: String(letter), range: Range<Int>(start: 2, end: 5) ) {
print(index)
}
}
}
I'm just completely lost on how to fill in that range part. There keep getting errors. I want to write a while loop which checks for the index of a character in a string. When found it will update a variabele which I can insert in range so it skips the part next time in the whole loop that contained the position of the character found before. Hope this makes it a bit clear. Here's some pseaduo code:
extension func
let range = 0
while loop: checks if character is in string
update range to index
append index to NSarray list
return nsarray list and if none found return nil
This is your extension written following the Functional Programming approach.
extension String {
func indexesOfChar(c: Character) -> [Int] {
return characters
.enumerate()
.filter { $0.element == c }
.map { $0.index }
}
}
Test
"Luminetic Land".indexesOfChar("i") // [3, 7]
Just for reference yourString.characters.indexOf("a") will give you the index of the first appearance of "a" in yourString. You could use this in a while loop to find "a" in the range from the previous index of "a" plus one and then add the indexes to an array until the output is negative one.