36

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".

Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
Piercy
  • 473
  • 1
  • 6
  • 11

15 Answers15

38

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, "")
// 
Liron Yahdav
  • 10,152
  • 8
  • 68
  • 104
Nate Cook
  • 92,417
  • 32
  • 217
  • 178
  • String.characters() is deprecated in swift 4. Alternate ? – Awais Fayyaz Apr 02 '18 at 06:27
  • @AwaisFayyaz in Swift 4 you can change `var chars = Array(myString.characters)` to `var chars = Array(myString)` to fix the deprecation warning. Source: https://stackoverflow.com/a/25921323/62 – Liron Yahdav Jul 27 '18 at 04:43
  • @LironYahdav Please add this in Answer. Thanks – Awais Fayyaz Jul 27 '18 at 05:00
  • This causes at least 1 copy (if not more), while the Swift team didn't allow the feature just to prevent that !! Anyone interested in copy-free version [see my answer](https://stackoverflow.com/a/67653964/8740349) (to only edit one node in the linked-list, which String internally could be, instead of copying) – Top-Master May 22 '21 at 20:49
22

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
Igor Kulagin
  • 1,701
  • 15
  • 20
22

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.

Luca Torella
  • 7,974
  • 4
  • 38
  • 48
7

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
SuryaKantSharma
  • 1,113
  • 12
  • 27
codester
  • 36,891
  • 10
  • 74
  • 72
  • what is bridgeToObjectiveC() – kumaresh Jan 08 '21 at 10:36
  • This causes at least 1 copy (if not more), while the Swift team didn't allow the feature just to prevent that !! Anyone interested in copy-free version [see my answer](https://stackoverflow.com/a/67653964/8740349) (to only edit one node in the linked-list, which String internally could be, instead of copying) – Top-Master May 22 '21 at 21:01
2

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) 
VicSF
  • 21
  • 1
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Nov 27 '21 at 19:10
1

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"

Greg
  • 515
  • 1
  • 8
  • 19
1

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"
Yurii Koval
  • 419
  • 5
  • 16
0

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.

Joshcamas
  • 31
  • 6
0
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.

Rivera
  • 10,792
  • 3
  • 58
  • 102
0

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.
0
        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.

0

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).

Top-Master
  • 7,611
  • 5
  • 39
  • 71
0
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();

}
Jorge Morgado
  • 1,148
  • 7
  • 23
-1

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.

Antonio
  • 71,651
  • 11
  • 148
  • 165
  • Actually, your code is wrong. And you can even find out why in the blog post you are referring to. The `substringToIndex` and `substringFromIndex` are `NSString` methods, and they use indices as defined for `NSString`, on the other hand you calculate `len` using `countElements`. You should either stick with `NSString`s indexing and length (`utf16count` in Swift) or `String`s. They do differ. – slazyk Jul 16 '14 at 23:18
  • Ok my bad, I didn't check the source of what intellisense suggested me. Thanks for pointing that out, I appreciate it and I accept the downvote. – Antonio Jul 17 '14 at 08:51
-1

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)
}
Piercy
  • 473
  • 1
  • 6
  • 11