我一直在更新我的一些旧代码和答案与Swift 3,但当我得到Swift字符串和索引子字符串的事情变得令人困惑。

具体来说,我尝试了以下几点:

let str = "Hello, playground"
let prefixRange = str.startIndex..<str.startIndex.advancedBy(5)
let prefix = str.substringWithRange(prefixRange)

第二行给出了如下错误

String类型的值没有成员substringWithRange

我看到String现在确实有以下方法:

str.substring(to: String.Index)
str.substring(from: String.Index)
str.substring(with: Range<String.Index>)

这些一开始让我很困惑,所以我开始摆弄索引和范围。这是子字符串的后续问题和答案。我在下面添加了一个答案来说明它们是如何使用的。


当前回答

String的特殊性在其他答案中已经讨论过了。解释一下:字符串有一个特定的索引,它不是Int类型,因为字符串元素在一般情况下没有相同的大小。因此,String不符合RandomAccessCollection,访问特定索引意味着遍历集合,这不是O(1)操作。

许多答案都提出了使用范围的变通方法,但它们会导致代码效率低下,因为它们使用的String方法(index(from:), index(:offsetBy:),…)不是O(1)。

要像在数组中一样访问字符串元素,您应该使用array:

let array = Array("Hello, world!")
let letter = array[5]

这是一个权衡,数组创建是一个O(n)操作,但数组访问是O(1)。当你想用String(数组)时,你可以转换回String。

其他回答

斯威夫特5 let desiredIndex: Int = 7 let substring = str[字符串]。指数(encodedOffset: desiredIndex)…] 这个子字符串变量会给你结果。 这里Int被转换为Index,然后你可以拆分字符串。除非你会得到错误。

斯威夫特 4+

extension String {
    func take(_ n: Int) -> String {
        guard n >= 0 else {
            fatalError("n should never negative")
        }
        let index = self.index(self.startIndex, offsetBy: min(n, self.count))
        return String(self[..<index])
    }
}

返回前n个字符的子序列,如果字符串较短,则返回整个字符串。(灵感来源:https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/take.html)

例子:

let text = "Hello, World!"
let substring = text.take(5) //Hello

下面是一个更通用的实现:

这种技术仍然使用索引来保持Swift的标准,并暗示一个完整的字符。

extension String
{
    func subString <R> (_ range: R) -> String? where R : RangeExpression, String.Index == R.Bound
    {
        return String(self[range])
    }

    func index(at: Int) -> Index
    {
        return self.index(self.startIndex, offsetBy: at)
    }
}

从第3个字符开始子字符串:

let item = "Fred looks funny"
item.subString(item.index(at: 2)...) // "ed looks funny"

我已经使用驼峰subString表示它返回一个字符串,而不是一个subString。

我对Swift的字符串访问模型感到非常沮丧:所有东西都必须是索引。我想要的只是使用Int访问字符串的第I个字符,而不是笨拙的索引和推进(这恰好随着每个主要版本的发布而改变)。所以我对String做了一个扩展:

extension String {
    func index(from: Int) -> Index {
        return self.index(startIndex, offsetBy: from)
    }

    func substring(from: Int) -> String {
        let fromIndex = index(from: from)
        return String(self[fromIndex...])
    }

    func substring(to: Int) -> String {
        let toIndex = index(from: to)
        return String(self[..<toIndex])
    }

    func substring(with r: Range<Int>) -> String {
        let startIndex = index(from: r.lowerBound)
        let endIndex = index(from: r.upperBound)
        return String(self[startIndex..<endIndex])
    }
}

let str = "Hello, playground"
print(str.substring(from: 7))         // playground
print(str.substring(to: 5))           // Hello
print(str.substring(with: 7..<11))    // play

String的特殊性在其他答案中已经讨论过了。解释一下:字符串有一个特定的索引,它不是Int类型,因为字符串元素在一般情况下没有相同的大小。因此,String不符合RandomAccessCollection,访问特定索引意味着遍历集合,这不是O(1)操作。

许多答案都提出了使用范围的变通方法,但它们会导致代码效率低下,因为它们使用的String方法(index(from:), index(:offsetBy:),…)不是O(1)。

要像在数组中一样访问字符串元素,您应该使用array:

let array = Array("Hello, world!")
let letter = array[5]

这是一个权衡,数组创建是一个O(n)操作,但数组访问是O(1)。当你想用String(数组)时,你可以转换回String。