Changing a character in String

This is what I've been doing to change a single letter at a specific location in a word:


let word = "abc"
var letters = Array(word.characters)
letters[1] = "x"
let newWord = String(letters)


Surely there's a more elegant way in Swift 3. But is this really it?


let index = word.index(word.startIndex, offsetBy: 1)
let range = index ..< word.index(after: index)
word.replacingCharacters(in: range, with: "x")


It works; but it looks hideous.

Accepted Reply

This code does not generate intermediate Array of Characters.

let word = "abc"
let index = word.index(word.startIndex, offsetBy: 1)
let newWord = word.replacingCharacters(in: index..<word.index(after: index), with: "x")

or

var mutableWord = "abc"
let index = mutableWord.index(mutableWord.startIndex, offsetBy: 1)
mutableWord.replaceSubrange(index..<mutableWord.index(after: index), with: "x")


But, in my oponion, this is far from elegant. Maybe we should wait until Swift 4 to get some elegant String APIs...

Replies

This code does not generate intermediate Array of Characters.

let word = "abc"
let index = word.index(word.startIndex, offsetBy: 1)
let newWord = word.replacingCharacters(in: index..<word.index(after: index), with: "x")

or

var mutableWord = "abc"
let index = mutableWord.index(mutableWord.startIndex, offsetBy: 1)
mutableWord.replaceSubrange(index..<mutableWord.index(after: index), with: "x")


But, in my oponion, this is far from elegant. Maybe we should wait until Swift 4 to get some elegant String APIs...

You must have posted this at the same time I added my second code example. So maybe that is the best way. But I certainly agree that it's far from elegant. Perhaps something can be done with an extension.

Sorry, when you added your second code, I may have been struggling with the Playground which demanded so many restarts, and I posted my article without knowing the update.


Writing such sort of extension is not a difficult thing:

var mutableWord = "abc"
extension String {
    subscript (at offset: Int)->Character {
        get {
            let index = self.index(self.startIndex, offsetBy: offset)
            return self.characters[index]
        }
        mutating set {
            let index = self.index(self.startIndex, offsetBy: offset)
            self.replaceSubrange(index...index, with: String(newValue))
        }
    }
}
mutableWord[at: 1] = "y"
print(mutableWord) //->ayc


One bad thing is this is not a standard.


Chris Lattner stated that String re-evaluation is one of the Swift 4 Stage 1 Goals:

- String re-evaluation: String is one of the most important fundamental types in the language. The standard library leads have numerous ideas of how to improve the programming model for it, without jeopardizing the goals of providing a unicode-correct-by-default model. Our goal is to be better at string processing than Perl!

(You can find this in the ML archive of swift-evolution-announce.)


I really expect better-than-Perl String APIs, and have decided not to make much private extensions.

  • replaceSubrange(index...index, with: CollectionOfOne(newValue))

Add a Comment

This is what I've been doing to change a single letter at a specific location in a word …

Where did you get that location? That is, in your example, where did the 1 come from?

The reason I ask is that numeric indexes like that rarely make sense in the general case of Unicode strings. For example, if you change the input of your original code to use a skin toned emoji, it’s results are less than ideal.

let word = "\u{1F466}\u{1F3FE}bc"
var letters = Array(word.characters) 
letters[1] = "x" 
let newWord = String(letters)      // "\u{1F466}xbc"

What you do about this depends on the constraints that you can apply to your strings. For example, if you knew that

word
was ASCII (it’s a string from an HTTP header, say) then you can ignore Unicode and treat it like an array of
UInt8
. OTOH, if
word
is derived from user text, you’ll need to rethink your approach.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"