0

Like in C, we can simply do

str[i] = str[j]

But how to write the similar logic in swift? Here is my code, but got error: Cannot assign through subscript: subscript is get-only

let indexI = targetString.index(targetString.startIndex, offsetBy: i)
let indexJ = targetString.index(targetString.startIndex, offsetBy: j)        
targetString[indexI] = targetString[indexJ]

I know it may work by using this method, but it's too inconvenient

replaceSubrange(, with: )
Hamish
  • 78,605
  • 19
  • 187
  • 280
Dustin Jia
  • 67
  • 7
  • 1
    You answered your own question by proving that subscript is get-only. – creeperspeak Mar 14 '17 at 21:14
  • I don't see why the subscript shouldn't be settable – it would functionally be the same as `.replaceSubrange(indexI...indexI, with: CollectionOfOne(targetString[indexJ]))`. You can always [file a bug/improvement](https://bugs.swift.org) over it. – Hamish Mar 14 '17 at 21:32
  • Check this: http://stackoverflow.com/questions/24092884/get-nth-character-of-a-string-in-swift-programming-language – agscastaneda Mar 14 '17 at 21:47
  • This drives everyone crazy. But that doesn't make this a good question. Basically, you are just complaining. That's good for a blog, but not for Stack Overflow. You already gave the correct answer as to how to do this with `replaceSubrange`, so the question is pointless. – matt Mar 14 '17 at 23:22
  • I'm not only complaining, I believe many people have the same feeling that managing String in Swift is painful comparing with other languages. Imaging you are doing coding interview with Swift, these tiny tedious syntaxes could drive me crazy and make bad impression for interviewer. – Dustin Jia Mar 15 '17 at 18:53

4 Answers4

3

In C, a string (char *) can be treated as an array of characters. In Swift, you can convert the String to an [Character], do the modifications you want, and then convert the [Character] back to String.

For example:

let str = "hello"    
var strchars = Array(str)    
strchars[0] = strchars[4]    
let str2 = String(strchars)
print(str2) // "oello"

This might seem like a lot of work for a single modification, but if you are moving many characters this way, you only have to convert once each direction.


Reverse a String

Here's an example of a simple algorithm to reverse a string. By converting to an array of characters first, this algorithm is similar to the way you might do it in C:

let str = "abcdefg"
var strchars = Array(str)

var start = 0
var end = strchars.count - 1

while start < end {
    let temp = strchars[start]
    strchars[start] = strchars[end]
    strchars[end] = temp
    start += 1
    end -= 1
}

let str2 = String(strchars)
print(str2)  // "gfedcba"
vacawama
  • 150,663
  • 30
  • 266
  • 294
  • Thank you! It's the kind of solution I was expected. In this way we could focus on the logic itself, other than struggling with the syntax. – Dustin Jia Mar 15 '17 at 18:56
1

Dealing with String with Swift is major pain in the a**. Unlike most languages I know that treat string as an array of characters, Swift treats strings as collection of extended grapheme clusters and the APIs to access them is really clumsy. Changes are coming in Swift 4 but that manifesto lost me about 10 paragraphs in.

Back to your question... you can replace the character like this:

var targetString = "Hello world"
let i = 0
let j = 1

let indexI = targetString.index(targetString.startIndex, offsetBy: i)
let indexJ = targetString.index(targetString.startIndex, offsetBy: j)

targetString.replaceSubrange(indexI...indexI, with: targetString[indexJ...indexJ])

print(targetString) // eello world
Code Different
  • 90,614
  • 16
  • 144
  • 163
  • Thanks for your explanation. I was hoping some way similar to C, that's why selected another answer, but your answer is also helpful. – Dustin Jia Mar 15 '17 at 18:57
0

I was quite shocked as well by the fact that swift makes string indexing so damn complicated. For that reason, I have built some string extensions that enable you to retrieve and change parts of strings based on indices, closed ranges, and open ranges, PartialRangeFrom, PartialRangeThrough, and PartialRangeUpTo. You can download the repository I created here

You can also pass in negative numbers in order to access characters from the end backwards.

public extension String {

    /**
     Enables passing in negative indices to access characters
     starting from the end and going backwards.
     if num is negative, then it is added to the
     length of the string to retrieve the true index.
     */
    func negativeIndex(_ num: Int) -> Int {
        return num < 0 ? num + self.count : num
    }

    func strOpenRange(index i: Int) -> Range<String.Index> {
        let j = negativeIndex(i)
        return strOpenRange(j..<(j + 1), checkNegative: false)
    }

    func strOpenRange(
        _ range: Range<Int>, checkNegative: Bool = true
    ) -> Range<String.Index> {

        var lower = range.lowerBound
        var upper = range.upperBound

        if checkNegative {
            lower = negativeIndex(lower)
            upper = negativeIndex(upper)
        }

        let idx1 = index(self.startIndex, offsetBy: lower)
        let idx2 = index(self.startIndex, offsetBy: upper)

        return idx1..<idx2
    }

    func strClosedRange(
        _ range: CountableClosedRange<Int>, checkNegative: Bool = true
    ) -> ClosedRange<String.Index> {

        var lower = range.lowerBound
        var upper = range.upperBound

        if checkNegative {
            lower = negativeIndex(lower)
            upper = negativeIndex(upper)
        }

        let start = self.index(self.startIndex, offsetBy: lower)
        let end = self.index(start, offsetBy: upper - lower)

        return start...end
    }

    // MARK: - Subscripts

    /**
     Gets and sets a character at a given index.
     Negative indices are added to the length so that
     characters can be accessed from the end backwards

     Usage: `string[n]`
     */
    subscript(_ i: Int) -> String {
        get {
            return String(self[strOpenRange(index: i)])
        }
        set {
            let range = strOpenRange(index: i)
            replaceSubrange(range, with: newValue)
        }
    }


    /**
     Gets and sets characters in an open range.
     Supports negative indexing.

     Usage: `string[n..<n]`
     */
    subscript(_ r: Range<Int>) -> String {
        get {
            return String(self[strOpenRange(r)])
        }
        set {
            replaceSubrange(strOpenRange(r), with: newValue)
        }
    }

    /**
     Gets and sets characters in a closed range.
     Supports negative indexing

     Usage: `string[n...n]`
     */
    subscript(_ r: CountableClosedRange<Int>) -> String {
        get {
            return String(self[strClosedRange(r)])
        }
        set {
            replaceSubrange(strClosedRange(r), with: newValue)
        }
    }

    /// `string[n...]`. See PartialRangeFrom
    subscript(r: PartialRangeFrom<Int>) -> String {

        get {
            return String(self[strOpenRange(r.lowerBound..<self.count)])
        }
        set {
            replaceSubrange(strOpenRange(r.lowerBound..<self.count), with: newValue)
        }
    }

    /// `string[...n]`. See PartialRangeThrough
    subscript(r: PartialRangeThrough<Int>) -> String {

        get {
            let upper = negativeIndex(r.upperBound)
            return String(self[strClosedRange(0...upper, checkNegative: false)])
        }
        set {
            let upper = negativeIndex(r.upperBound)
            replaceSubrange(
                strClosedRange(0...upper, checkNegative: false), with: newValue
            )
        }
    }

    /// `string[...<n]`. See PartialRangeUpTo
    subscript(r: PartialRangeUpTo<Int>) -> String {

        get {
            let upper = negativeIndex(r.upperBound)
            return String(self[strOpenRange(0..<upper, checkNegative: false)])
        }
        set {
            let upper = negativeIndex(r.upperBound)
            replaceSubrange(
                strOpenRange(0..<upper, checkNegative: false), with: newValue
            )
        }
    }


}

Usage:

let text = "012345"
print(text[2]) // "2"
print(text[-1] // "5"

print(text[1...3]) // "123"
print(text[2..<3]) // "2"
print(text[3...]) // "345"
print(text[...3]) // "0123"
print(text[..<3]) // "012"
print(text[(-3)...] // "345"
print(text[...(-2)] // "01234"

All of the above works with assignment as well. All subscripts have getters and setters.

Peter Schorn
  • 916
  • 3
  • 10
  • 20
0

a new extension added,

since String conforms to BidirectionalCollection Protocol

extension String{
    subscript(at i: Int) -> String? {
            get {
                if i < count{
                    let idx = index(startIndex, offsetBy: i)
                    return String(self[idx])
                }
                else{
                    return nil
                }
            }
            set {
                if i < count{
                    let idx = index(startIndex, offsetBy: i)
                    remove(at: idx)
                    if let new = newValue, let first = new.first{
                        insert(first, at: idx)
                    }
                }
            }
        }
}

call like this:

var str = "fighter"

str[at: 2] = "6"
black_pearl
  • 2,549
  • 1
  • 23
  • 36