How would the extension work in swift 3? - string

I am using the below extension in swift 3, but I know it is incorrect because I still get errors. The point of the extension is to get a range of a string using the syntax string[0...2] That will return the string from 0. This is the error I am getting
extension String {
subscript (i: Int) -> Character {
return self[self.characters.index(self.startIndex, offsetBy: i)]
}
subscript (i: Int) -> String {
return String(self[i] as Character)
}
subscript (r: Range<Int>) -> String {
let start = characters.index(startIndex, offsetBy: r.lowerBound)
let end = characters.index(start, offsetBy: r.upperBound - r.lowerBound)
return self[(start ..< end)]
}
}

In Swift 3, the closed range operator ... generates CountableClosedRange for Ints. So you need to define one more overload for subscript.
subscript (r: CountableClosedRange<Int>) -> String {
let start = characters.index(startIndex, offsetBy: r.lowerBound)
let end = characters.index(start, offsetBy: r.upperBound - r.lowerBound)
return self[(start ... end)]
}

Updated in Swift 4:
extension String{
subscript (r: CountableClosedRange<Int>) -> String {
let start = index(startIndex, offsetBy: r.lowerBound)
let end = index(start, offsetBy: r.upperBound - r.lowerBound)
return String(self[start...end])
}
}

Related

String, substring, Range, NSRange in Swift 4

I am using the following code to get a String substring from an NSRange:
func substring(with nsrange: NSRange) -> String? {
guard let range = Range.init(nsrange)
else { return nil }
let start = UTF16Index(range.lowerBound)
let end = UTF16Index(range.upperBound)
return String(utf16[start..<end])
}
(via: https://mjtsai.com/blog/2016/12/19/nsregularexpression-and-swift/)
When I compile with Swift 4 (Xcode 9b4), I get the following errors for the two lines that declare start and end:
'init' is unavailable
'init' was obsoleted in Swift 4.0
I am confused, since I am not using an init.
How can I fix this?
Use Range(_, in:) to convert an NSRange to a Range in Swift 4.
extension String {
func substring(with nsrange: NSRange) -> Substring? {
guard let range = Range(nsrange, in: self) else { return nil }
return self[range]
}
}
With Swift 4 we can get substrings this way.
Substring from index
let originStr = "Test"
let offset = 1
let str = String(originStr.suffix(from: String.Index.init(encodedOffset: offset)))
Substring to index
let originStr = "Test"
let offset = 1
String(self.prefix(index))

Swift 2.2 loop through string and get char & index

I'm trying to loop through a String get get the characters as well as their index. Sounds easy.. but I'm not finding the correct syntax for doing that.
Here is what I was trying:
let str = "testString"
for (index, char) in str.characters {
print(index)
print(char)
}
or
for (index, char) in enumerate(str.characters) {...}
let str = "testString"
for (index, element) in str.characters.enumerate() {
print(index)
print(element)
}
Swift 4
let str = "Swift 4"
for (index, element) in str.enumerated() {
print(index)
print(element)
}

find the position of character in a substring [duplicate]

This question already has an answer here:
how to find the index of a character in a string from specific position
(1 answer)
Closed 6 years ago.
How can I find the first position of a character in a substring. Not in the string overall, but the first after a specified character position.
Example:
var str = "This is a test string"
//find the position of first "i" after "is"
let position = str.firstPositionOfIAfterPosition(5) // returns 18
I know I can find the overall first position with code below. How can I extend this to start looking only after a specified character position?
let position = str.rangeOfString("i").startIndex
var s = "This is a test string"
var targetRange = s.characters.indices
targetRange.startIndex = targetRange.startIndex.advancedBy(6) // skip past
let r = s.rangeOfString("i", options: [], range: targetRange, locale: nil)
// 18..<19
var str = "This is a test string"
func getIndexAfterString(string: String) -> Int {
let firstIndex = str.rangeOfString(string)?.startIndex.advancedBy(string.characters.count)
let i: Int = str.startIndex.distanceTo(firstIndex!)
let secondIndex = str.substringFromIndex(firstIndex!).rangeOfString("i")?.startIndex
let j: Int = str.startIndex.distanceTo(secondIndex!)
return i + j
}
let index: Int = getIndexAfterString(" is ") //18
Similar to matt's answer, but as String extension and with error handling
extension String {
func firstPositionOf(string: String, afterPosition index: Int) -> String.Index?
{
if self.isEmpty || self.characters.count - 1 < index { return nil }
let subRange = Range<String.Index>(self.startIndex.advancedBy(index + 1)..<self.endIndex)
guard let foundRange = self.rangeOfString(string, options: [], range: subRange) else { return nil }
return foundRange.startIndex
}
}
let str = "This is a test string"
let position = str.firstPositionOf("i", afterPosition:5) // -> 18

How doing String-Programming in Swift

I miss usable String-functions, that are easy to use, without typing lines of strange identifiers. So I decided to built up a libary with useful and recognicable String-Functions.
I first tried to use Cocoa String-Functions to solve this problem. So I tried in the playground:
import Cocoa
func PartOfString(s: String, start: Int, length: Int) -> String
{
return s.substringFromIndex(advance(s.startIndex, start - 1)).substringToIndex(advance(s.startIndex, length))
}
PartOfString("HelloaouAOUs.World", 1, 5) --> "Hello"
PartOfString("HelloäöüÄÖÜß…World", 1, 5) --> "Hello"
PartOfString("HelloaouAOUs.World", 1, 18) --> "HelloaouAOUs.World"
PartOfString("HelloäöüÄÖÜß…World", 1, 18) --> "HelloäöüÄÖÜß…World"
PartOfString("HelloaouAOUs.World", 6, 7) --> "aouAOUs"
PartOfString("HelloäöüÄÖÜß…World", 6, 7) --> "äöüÄO"
If UnCode Characters are in the String for the case, that "substringFromIndex" is not the Start-Index. And even worse, the Swift-Program crashes sometimes at running time, if UnCode-Characters are in a String, for the case, that "substringFromIndex" is not the Start-Index. So I decided to create a set of new Functions, that take care of this problem and work with UnCode-Characters. Please note, that filenames can contain UnCode-Characters as well. So if you think you do not need UnCode-Characters you are wrong.
If you want to reproduce this, you need the same String I used, because copying from this Web-Page does not reproduce the problem.
var s: String = "HelloäöüÄÖÜß…World"
var t: String = s.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
var u: String = "Helloa%CC%88o%CC%88u%CC%88A%CC%88O%CC%88U%CC%88%C3%9F%E2%80%A6World".stringByRemovingPercentEncoding!
var b: Bool = (s == u) --> true
PartOfString(s, 6, 7) --> "äöüÄO"
Now you could get the idea, to convert the disturbing Canonical-Mapping UniCodes to compatible one with the following function:
func percentescapesremove (s: String) -> String
{
return (s.stringByRemovingPercentEncoding!.precomposedStringWithCompatibilityMapping)
}
And the result you will get is:
var v: String = percentescapesremove(t) --> "HelloäöüÄÖÜß...World"
PartOfString(v, 6, 7) --> "äöüÄÖÜß"
var a: Bool = (s == v) --> false
When you do so, the "äöüÄÖÜß" looks good and you think, everything is OK but look at the "..." which has been permanently converted from UniCode "…" to non-UniCode "..." and has the result which is not identically to the first string. If you have UniCode-filenames, then converting will result in not finding the file on a volume. So it is a good idea to convert only for scree-output and keep the original String in a save place.
The problem with the PartOfString-Function above is, that it generates a new String in the first part of the assignment and uses this new String with the index of the old one, which does not work, because the UniCodes have a different length than the normal letters. So I improved the funktion (thank to Martin R for his help):
func NewPartOfString(s: String, start: Int, length: Int) -> String
{
let t: String = s.substringFromIndex(advance(s.startIndex, start - 1))
return t.substringToIndex(advance(t.startIndex, length))
}
And the result is correct:
NewPartOfString("HelloaouAOUs.World", 1, 5) --> "Hello"
NewPartOfString("HelloäöüÄÖÜß…World", 1, 5) --> "Hello"
NewPartOfString("HelloaouAOUs.World", 1, 18) --> "HelloaouAOUs.World"
NewPartOfString("HelloäöüÄÖÜß…World", 1, 18) --> "HelloäöüÄÖÜß…World"
NewPartOfString("HelloaouAOUs.World", 6, 7) --> "aouAOUs"
NewPartOfString("HelloäöüÄÖÜß…World", 6, 7) --> "äöüÄÖÜß"
In the next step I will show a few functions, that can be used and work well. All of them are based on Integer-Index-Values that will start at 1 for the first character end end with the index for the last character being identically to the length of the String.
This function returns the length of a string:
func len (s: String) -> Int
{
return (countElements(s)) // This works not really fast, because of UniCode
}
This function returns the UniCode-Number of the first UniCode-Character in the String:
func asc (s: String) -> Int
{
if (s == "")
{
return 0
}
else
{
return (Int(s.unicodeScalars[s.unicodeScalars.startIndex].value))
}
}
This function returns the UniCode-Character of the given UniCode-Number:
func char (c: Int) -> String
{
var s: String = String(UnicodeScalar(c))
return (s)
}
This function returns the Upper-Case representation of a String:
func ucase (s: String) -> String
{
return (s.uppercaseString)
}
This function returns the Lower-Case representation of a String:
func lcase (s: String) -> String
{
return (s.lowercaseString)
}
The next Function gives the left part of a String with a given length:
func left (s: String, length: Int) -> String
{
if (length < 1)
{
return ("")
}
else
{
if (length > len(s))
{
return (s)
}
else
{
return (s.substringToIndex(advance(s.startIndex, length)))
}
}
}
The next Function gives the right part of a String with a given length:
func right (s: String, laenge: Int) -> String
{
var L: Int = len(s)
if (L <= laenge)
{
return(s)
}
else
{
if (laenge < 1)
{
return ("")
}
else
{
let t: String = s.substringFromIndex(advance(s.startIndex, L - laenge))
return t.substringToIndex(advance(t.startIndex, laenge))
}
}
}
The next Function gives the part of a String with a given length:
func mid (s: String, start: Int, laenge: Int) -> String
{
if (start <= 1)
{
return (left(s, laenge))
}
else
{
var L: Int = len(s)
if ((start > L) || (laenge < 1))
{
return ("")
}
else
{
if (start + laenge > L)
{
let t: String = s.substringFromIndex(advance(s.startIndex, start - 1))
return t.substringToIndex(advance(t.startIndex, L - start + 1))
}
else
{
let t: String = s.substringFromIndex(advance(s.startIndex, start - 1))
return t.substringToIndex(advance(t.startIndex, laenge))
}
}
}
}
A little more difficult is to get a character at a given position, because we cannot use "substringFromIndex" and "substringToIndex" with "substringFromIndex" is not the Start-Index. So the idea is to trace through the string, character for character, and get the needed substring.
func CharacterOfString(s: String, index: Int, length: Int) -> String
{
var c: String = ""
var i: Int = 0
for UniCodeChar in s.unicodeScalars
{
i = i + 1
if ((i >= index) && (i < index + length))
{
c = c + String(UniCodeChar)
}
}
return (c)
}
But this works not correctly for Strings which contain UniCode-Characters. The following examples show what happens:
CharacterOfString("Swift Example Text aouAOUs.", 16, 8) --> "ext aouA"
len(CharacterOfString("Swift Example Text aouAOUs.", 16, 8)) --> 8
CharacterOfString("Swift Example Text äöüÄÖÜß…", 16, 8) --> "ext äö"
len(CharacterOfString("Swift Example Text äöüÄÖÜß…", 16, 8)) --> 6
So we see, that the resulting String is too short, because a UniCode-Character can contain more than one character. This is because "ä" can be one UniCode-Character and also written as two "a¨" UniCode-Character. So we need another way to get a valid substring.
The solution is, to convert the UniCode-String to an array of UniCode-Characters and to use the index af the array to get a valid character. This works in all cases to get a single Character of an UniCode-String at a given index:
func indchar (s: String, i: Int) -> String
{
if ((i < 1) || (i > len(s)))
{
return ("")
}
else
{
return String(Array(s)[i - 1])
}
}
And with this knowledge, I have built a Function, which can get a valid UniCode-Substring with a given Start-Index and a given length:
func substring(s: String, Start: Int, Length: Int) -> String
{
var L: Int = len(s)
var UniCode = Array(s)
var result: String = ""
var TheEnd: Int = Start + Length - 1
if ((Start < 1) || (Start > L))
{
return ("")
}
else
{
if ((Length < 0) || (TheEnd > L))
{
TheEnd = L
}
for var i: Int = Start; i <= TheEnd; ++i
{
result = result + String(UniCode[i - 1])
}
return (result)
}
}
The next Function searches for the position of a given String in another String:
func position (original: String, search: String, start: Int) -> Int
{
var index = part(original, start).rangeOfString(search)
if (index != nil)
{
var pos: Int = distance(original.startIndex, index!.startIndex)
return (pos + start)
}
else
{
return (0)
}
}
This function looks, if a given Character-Code is a number (0-9):
func number (n: Int) -> Bool
{
return ((n >= 48) & (n <= 57)) // "0" to "9"
}
Now the basic String-Operations are shown, but what about Numbers? How will numbers converted to Strings and vice versa? Let's have a look at converting Strings to Numbers. Please not the "!" in the second line, which is used to get a Int and not an optional Int.
var s: String = "123" --> "123"
var v: Int = s.toInt() --> (123)
var v: Int = s.toInt()! --> 123
But this does not work, if the String contains some characters:
var s: String = "123." --> "123."
var v: Int = s.toInt()! --> Will result in a Runtime Error, because s.toInt() = nil
So I decided to built a smater Function to get the value of a String:
func val (s: String) -> Int
{
var p: Int = 0
var sign: Int = 0
if (indchar(s, 1) == "-")
{
sign = 1
p = 1
}
while(number(asc(indchar(s, p + 1))))
{
p = p + 1
}
if (p > sign)
{
return (left(s, p).toInt()!)
}
else
{
return (0)
}
}
Now the result is correct and does not produce a Runtime-Error:
var s: String = "123." --> "123."
var v: Int = val(s) --> 123
And now the same for Floating-Point Numbers:
func realval (s: String) -> Double
{
var r: Double = 0
var p: Int = 1
var a: Int = asc(indchar(s, p))
if (indchar(s, 1) == "-")
{
p = 2
}
while ((a != 44) && (a != 46) && ((a >= 48) & (a <= 57)))
{
p = p + 1
a = asc(indchar(s, p))
}
if (p >= len(s)) // Integer Number
{
r = Double(val(s))
}
else // Number with fractional part
{
var mantissa: Int = val(substring(s, p + 1, -1))
var fract: Double = 0
while (mantissa != 0)
{
fract = (fract / 10) + (Double(mantissa % 10) / 10)
mantissa = mantissa / 10
p = p + 1
}
r = Double(val(s)) + fract
p = p + 1
}
a = asc(indchar(s, p))
if ((a == 69) || (a == 101)) // Exponent
{
var exp: Int = val(substring(s, p + 1, -1))
if (exp != 0)
{
for var i: Int = 1; i <= abs(exp); ++i
{
if (exp > 0)
{
r = r * 10
}
else
{
r = r / 10
}
}
}
}
return (r)
}
This works for Floating points numbers with exponents:
var s: String = "123.456e3"
var t: String = "123.456e-3"
var v: Double = realval(s) --> 123456
var w: Double = realval(t) --> 0.123456
To generate a String from an Integer is much more simple:
func str (n: Int) -> String
{
return (String(n))
}
A String of a floating point variable does not work with String(n) but can be done with:
func strreal (n: Double) -> String
{
return ("\(n)")
}

How to replace nth character of a string with another

How could I replace nth character of a String with another one?
func replace(myString:String, index:Int, newCharac:Character) -> String {
// Write correct code here
return modifiedString
}
For example, replace("House", 2, "r") should be equal to "Horse".
Solutions that use NSString methods will fail for any strings with multi-byte Unicode characters. Here are two Swift-native ways to approach the problem:
You can use the fact that a String is a sequence of Character to convert the string to an array, modify it, and convert the array back:
func replace(myString: String, _ index: Int, _ newChar: Character) -> String {
var chars = Array(myString) // gets an array of characters
chars[index] = newChar
let modifiedString = String(chars)
return modifiedString
}
replace("House", 2, "r")
// Horse
Alternately, you can step through the string yourself:
func replace(myString: String, _ index: Int, _ newChar: Character) -> String {
var modifiedString = String()
for (i, char) in myString.characters.enumerate() {
modifiedString += String((i == index) ? newChar : char)
}
return modifiedString
}
Since these stay entirely within Swift, they're both Unicode-safe:
replace("🏠🏡🏠🏡🏠", 2, "🐴")
// 🏠🏡🐴🏡🏠
In Swift 4 it's much easier.
let newString = oldString.prefix(n) + char + oldString.dropFirst(n + 1)
This is an example:
let oldString = "Hello, playground"
let newString = oldString.prefix(4) + "0" + oldString.dropFirst(5)
where the result is
Hell0, playground
The type of newString is Substring. Both prefix and dropFirst return Substring. Substring is a slice of a string, in other words, substrings are fast because you don't need to allocate memory for the content of the string, but the same storage space as the original string is used.
I've found this solution.
var string = "Cars"
let index = string.index(string.startIndex, offsetBy: 2)
string.replaceSubrange(index...index, with: "t")
print(string)
// Cats
Please see NateCook answer for more details
func replace(myString: String, _ index: Int, _ newChar: Character) -> String {
var chars = Array(myString.characters) // gets an array of characters
chars[index] = newChar
let modifiedString = String(chars)
return modifiedString
}
For Swift 5
func replace(myString: String, _ index: Int, _ newChar: Character) -> String {
var chars = Array(myString) // gets an array of characters
chars[index] = newChar
let modifiedString = String(chars)
return modifiedString
}
replace("House", 2, "r")
This is no longer valid and deprecated.
You can always use swift String with NSString.So you can call NSString function on swift String.
By old stringByReplacingCharactersInRange: you can do like this
var st :String = "House"
let abc = st.bridgeToObjectiveC().stringByReplacingCharactersInRange(NSMakeRange(2,1), withString:"r") //Will give Horse
For modify existing string:
extension String {
subscript(_ n: Int) -> Character {
get {
let idx = self.index(startIndex, offsetBy: n)
return self[idx]
}
set {
let idx = self.index(startIndex, offsetBy: n)
self.replaceSubrange(idx...idx, with: [newValue])
}
}
}
var s = "12345"
print(s[0])
s[0] = "9"
print(s)
I've expanded upon Nate Cooks answer and transformed it into a string extension.
extension String {
//Enables replacement of the character at a specified position within a string
func replace(_ index: Int, _ newChar: Character) -> String {
var chars = Array(characters)
chars[index] = newChar
let modifiedString = String(chars)
return modifiedString
}
}
usage:
let source = "House"
let result = source.replace(2,"r")
result is "Horse"
I think what #Greg was trying to achieve with his extension is this:
mutating func replace(characterAt index: Int, with newChar: Character) {
var chars = Array(characters)
if index >= 0 && index < self.characters.count {
chars[index] = newChar
let modifiedString = String(chars)
self = modifiedString
} else {
print("can't replace character, its' index out of range!")
}
}
usage:
let source = "House"
source.replace(characterAt: 2, with: "r") //gives you "Horse"
After looking at the Swift Docs, I managed to make this function:
//Main function
func replace(myString:String, index:Int, newCharac:Character) -> String {
//Looping through the characters in myString
var i = 0
for character in myString {
//Checking to see if the index of the character is the one we're looking for
if i == index {
//Found it! Now instead of adding it, add newCharac!
modifiedString += newCharac
} else {
modifiedString += character
}
i = i + 1
}
// Write correct code here
return modifiedString
}
Please note that this is untested, but it should give you the right idea.
func replace(myString:String, index:Int, newCharac:Character) -> String {
var modifiedString = myString
let range = Range<String.Index>(
start: advance(myString.startIndex, index),
end: advance(myString.startIndex, index + 1))
modifiedString.replaceRange(range, with: "\(newCharac)")
return modifiedString
}
I would prefer to pass a String than a Character though.
Here's a way to replace a single character:
var string = "This is the original string."
let offset = 27
let index = string.index(string.startIndex, offsetBy: offset)
let range = index...index
print("ORIGINAL string: " + string)
string.replaceSubrange(range, with: "!")
print("UPDATED string: " + string)
// ORIGINAL string: This is the original string.
// UPDATED string: This is the original string!
This works with multi-character strings as well:
var string = "This is the original string."
let offset = 7
let index = string.index(string.startIndex, offsetBy: offset)
let range = index...index
print("ORIGINAL string: " + string)
string.replaceSubrange(range, with: " NOT ")
print("UPDATED string: " + string)
// ORIGINAL string: This is the original string.
// UPDATED string: This is NOT the original string.
var s = "helloworld"
let index = ((s.count) / 2) // index is 4
let firstIndex = s.index(s.startIndex, offsetBy: index)
let secondIndex = s.index(s.startIndex, offsetBy: index)
s.replaceSubrange(firstIndex...secondIndex, with: "*")
print("Replaced string is: \(s)") //OUTPUT IS: hell*world
This is working fine to replace string using the index.
String class in Swift (till v5 and maybe later) is what other languages call a StringBuilder class, and for performance reasons, Swift does NOT provide setting character by index; If you don't care about performance a simple solution could be:
public static func replace(_ string: String, at index: Int, with value: String) {
let start = string.index(string.startIndex, offsetBy: index)
let end = string.index(start, offsetBy: 1)
string.replaceSubrange(start..<end, with: value)
}
Or as an extension:
extension String {
public func charAt(_ index: Int) -> Character {
return self[self.index(self.startIndex, offsetBy: index)];
}
public mutating func setCharAt(_ index: Int, _ new: Character) {
self.setCharAt(index, String(new))
}
public mutating func setCharAt(_ index: Int, _ new: String) {
let i = self.index(self.startIndex, offsetBy: index)
self.replaceSubrange(i...i, with: new)
}
}
Note how above needs to call index(...) method to convert integer to actual-index!? It seems, Swift implements String like a linked-list, where append(...) is really fast, but even finding the index (without doing anything with it) is a linear-time operation (and gets slower based on concatenation count).
public void createEncodedSentence() {
StringBuffer buff = new StringBuffer();
int counter = 0;
char a;
for (int i = 0; i < sentence.length(); i++) {
a = sentence.charAt(i);
if (a == '.') {
buff.append('*');
}
if (a != ' ' && a != '.') {
counter++;
}
if (counter % 3 == 0) {
buff.append("");
}
buff.append(sentence.charAt(i));
}
encodedSentence = buff.toString();
}
Strings in swift don't have an accessor to read or write a single character. There's an excellent blog post by Ole Begemann describing how strings in swift work.
Note: the implementation below is wrong, read addendum
So the right way is by taking the left part of the string up to the index -1 character, append the replacing character, then append the string from index + 1 up to the end:
func myReplace(myString:String, index:Int, newCharac:Character) -> String {
var modifiedString: String
let len = countElements(myString)
if (index < len) && (index >= 0) {
modifiedString = myString.substringToIndex(index) + newCharac + myString.substringFromIndex(index + 1)
} else {
modifiedString = myString
}
return modifiedString
}
Note: in my implementation I chose to return the original string if the index is not in a valid range
Addendum Thanks to #slazyk, who found out that my implementation is wrong (see comment), I am providing a new swift only version of the function.
func replace(myString:String, index:Int, newCharac:Character) -> String {
var modifiedString: String
if (index < 0) || (index >= countElements(myString)) {
modifiedString = myString
} else {
var start = myString.startIndex
var end = advance(start, index)
modifiedString = myString[start ..< end]
modifiedString += newCharac
start = end.successor()
end = myString.endIndex
modifiedString += myString[start ... end]
}
return modifiedString
}
#codester's answer looks very good, and it's probably what I would use myself.
It would be interesting to know how performances compare though, using a fully swift solution and bridging to objective-c instead.
Here is an efficient answer :
import Foundation
func replace(myString:String, index:Int, newCharac:Character) -> String {
return myString.substringToIndex(index-1) + newCharac + myString.substringFromIndex(index)
}

Resources