String replacement of character at

Hi,

I'm very new at Swift, and I wonder what's the reason behind the snippet below not being possible.

var str = "My A String"
str[3] = "B"
//or
str.replace(at: 3, with: "B")

I know there are ways of doing this and extend String. I wonder, why isn't this already in the Standard Library.

I'm using Swift 5.1

1 Like

It’s mentioned in the documentation:

different characters can require different amounts of memory to store, so in order to determine which Character is at a particular position, you must iterate over each Unicode scalar from the start or end of that String . For this reason, Swift strings can’t be indexed by integer values.

I highly recommend you read through the Strings and Characters chapter for a better understanding of how Strings work.

I highly recommend you read through the Strings and Characters chapter for a better understanding of how Strings work.

I did.

I'm not talking about memory. Whatever size the characters have you can always count them by using an integer. If the first character takes 3 bytes, and the second character takes 6 bytes, and the third takes 1 bytes. When I do str[3] = "B" I mean, replace the fourth character, wherever it is in memory for this new character (starting with byte 11 in the example before).

I'll show you:

var index: Int = 0;
var newStr = ""
for c in str {
    if index == 3 {
        newStr.append("B")
    } else {
        newStr.append(c)
    }
    index += 1
}

Heck, I can also show you this non-sense

let rs = str.index(str.startIndex, offsetBy: 3) // See how 3 is an Int
let re = str.index(str.startIndex, offsetBy: 4) // See how 4 is an Int
str.replaceSubrange(rs..<re, with: "B")

Well, the subscript operator is documented to be O(1), however String cannot provide this guarantee with an Int index because it can't get you the nth character without evaluating all the characters before it. You have to use the String.Index subscript instead.

let rs = str.index(str.startIndex, offsetBy: 3)

This takes O(n) time.

So, you're saying the reason why that is not provided with the standard library is performance?

What's the other option then:

  • Have people do it?
  • Or use the str.index(str.startIndex, offsetBy: 3) and forget about performance?

The thing is, with String being composed of Unicode characters there's no way of figuring out a character in a nth-position without iteration (or some magic in the underlying structure). That was a known disadvantage of the String class design. That doesn't justify, or explain, why the use case above is not covered in the standard library.

I think that's definitely one of the main reasons, if not the reason. This discussion comes up every now and then, so I think it would be good for you to read through some existing posts which go into much more detail:

https://forums.swift.org/t/why-are-string-offsets-so-complicated/
https://forums.swift.org/t/how-to-make-string-index-with-int-offset-distance-in-o-1-time

2 Likes

Ok, now I've read a couple of those discussions and I have a one suggestion to whoever writes the documentation, call it what it is:

For this reason, Swift strings can’t be indexed by integer values.

should be

The String type is not optimized for random access and we want to discourage developers of using it that way.

The reason why the API doesn't cover the use case from above is because Swift developers are worried about people shooting themselves in the foot with it. One thread says this issue comes up every so often. I would, therefore, have them rethink the position. I would gladly favor ease of use over performance in this case.

The other thing I would suggest is to be explicit, from what I've read with Swift, there's so much misleading stuff in the standard library that unless the documentation is abundant and explicit, any efforts to prevent people misusing the language are going to be futile.

In any case, thanks for pointing me to those posts. I did some searching but those didn't come up.

You can always write a method/subscript on String to do that for you, if you want convenience. I wouldn’t personally do it though and I’d rather be explicit everywhere.

You can file an improvement ticket at bugs.swift.org and someone should take a look.

SE–265 “Offset-Based Access to Indices, Elements, and Slices” would address this use-case.

2 Likes