String子串是如何在Swift中工作的

我一直在更新一些旧的代码和答案与Swift 3,但是当我到了Swift的string和索引与子串的事情变得混乱。

具体来说我正在尝试以下内容:

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

第二行给了我下面的错误

types'String'的值没有成员'substringWithRange'

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

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

起初我真的很困惑,所以我开始玩索引和范围 。 这是子串的后续问题和答案。 我在下面添加一个答案以显示如何使用它们。

在这里输入图像描述

以下所有示例均使用

 var str = "Hello, playground" 

子串(到:String.Index)

这将从string的开头的子string指定的索引。

 let index = str.index(str.startIndex, offsetBy: 5) str.substring(to: index) // Hello 

如果你不明白String.Index ,请参阅我以前的答案 。

子串(来自:String.Index)

将从指定索引到string结尾的子string。

 let index = str.index(str.startIndex, offsetBy: 7) str.substring(from: index) // playground 

子string(带:范围)

这个只是给你一个基于你传入的范围的子串。一旦你有了范围,这很容易。 虽然还不如旧的NSRange那么方便,但这是对Swift 2系列的一个明显改进。

 let start = str.index(str.startIndex, offsetBy: 7) let end = str.index(str.endIndex, offsetBy: -6) let range = start..<end str.substring(with: range) // play 

“这是一个痛苦!我只是使用一个Int索引扩展。”

在你做之前,我build议你阅读Airspeed Velocity和Ole Begemann出色的文章Strings in Swift 3 。 作者给出了几个理由,以避免像收集一样处理string。

  1. 很容易忘记字符集合的问题( 例子 ):

    由于某种原因,string不是collections品 – 这不仅仅是因为Swift团队忘了。 …尽pipe字符尽力将组合字符序列呈现为单个值,但仍然有些情况下,逐个字符地处理string会导致不正确的结果。

    为此,string的字符集视图被移动到了一个属性字符 ,这使得它与其他集合视图类似: unicodeScalars , utf8和utf16 。 select一个特定的视图会提示您确认您正在进入“收集处理”模式,您应该考虑将要运行的algorithm的后果。

  2. 这使得编写低效的代码变得非常容易。 提到创build一个下标扩展,他们写道:

    …就像扩展String来使其成为集合一样,这种扩展最好避免。 否则你可能会试图开始编写这样的代码:

     for i in 0..<5 { print(s[i]) } 

    但是就像这段代码看起来那么简单,这是非常低效的。 每次用一个整数访问s时,运行一个O(n)函数来提前它的起始索引。 在另一个线性循环内运行一个线性循环意味着这个for循环意外地是O(n²) – 随着string长度的增加,这个循环所花费的时间将以二次方式增加。

由于上述原因,我也不build议使用基于整数的string扩展。 不过,你可以自由地忽略这个build议。 如果你决定使用这个扩展,这个扩展看起来不错。 (请务必彻底testing您最终使用的任何扩展名。)

斯威夫特4

好消息。 看起来像Swift 4中的一些复杂性会降低,包括再次使string符合Collection。 阅读这个和更多的细节。

我非常沮丧在Swift的String访问模式:一切都必须是一个Index 。 我想要的只是使用Int来访问string的第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 substring(from: fromIndex) } func substring(to: Int) -> String { let toIndex = index(from: to) return substring(to: toIndex) } func substring(with r: Range<Int>) -> String { let startIndex = index(from: r.lowerBound) let endIndex = index(from: r.upperBound) return substring(with: 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 

斯威夫特4

在Swift 4 String符合Collection 。 而不是substring ,现在我们应该使用subscript. 所以,如果你只想从你好,游乐场只剪出单词游戏,你可以这样做:

 let start = str.index(str.startIndex, offsetBy: 7) let end = str.index(str.endIndex, offsetBy: -6) let result = str[start..<end] // The result is of type Substring 

有趣的是知道,这样做会给你一个Substring而不是一个String 。 当Substring与原始string共享存储时,这是快速高效的。 但是这种共享内存也很容易导致内存泄漏。

这就是为什么你应该把结果复制到一个新的String中,一旦你想清理原来的String。 你可以使用普通的构造函数来做到这一点:

 let newString = String(result) 

您可以在[Apple文档]中find有关新的Substring类的更多信息。 1

所以,如果你例如得到一个NSRegularExpression结果的Range ,你可以使用下面的扩展名:

 extension String { subscript(_ range: NSRange) -> String { let start = self.index(self.startIndex, offsetBy: range.lowerBound) let end = self.index(self.startIndex, offsetBy: range.upperBound) let subString = self[start..<end] return String(subString) } } 

我有同样的初始反应。 我也对每个主要版本的语法和对象如此剧烈地变化感到沮丧。

然而,我从经验中意识到,我总是最终忍受尝试与处理多字节字符的“改变”打交道的后果,如果你正在寻找全球的观众,这是不可避免的。

所以我决定承认和尊重苹果公司工程师所做的努力,并在他们提出这种“可怕的”方法时理解他们的想法。

而不是创build扩展,这只是一个解决scheme,使您的生活更轻松(我不是说他们错了或昂贵),为什么不弄清现在如何devisestring的工作。

例如,我有这个在Swift 2.2上工作的代码:

 let rString = cString.substringToIndex(2) let gString = (cString.substringFromIndex(2) as NSString).substringToIndex(2) let bString = (cString.substringFromIndex(4) as NSString).substringToIndex(2) 

在放弃尝试获得相同的方法(例如使用子串)之后,我终于理解了将string视为一个双向集合的概念,最终我得到了这个版本的相同的代码:

 let rString = String(cString.characters.prefix(2)) cString = String(cString.characters.dropFirst(2)) let gString = String(cString.characters.prefix(2)) cString = String(cString.characters.dropFirst(2)) let bString = String(cString.characters.prefix(2)) 

我希望这有助于…

我是新的Swift 3,但看起来比较类似的String (索引)语法我觉得索引就像是一个“指针”的string和Int可以帮助作为一个独立的对象。 使用base + offset语法,我们可以从string中得到第i个字符,代码如下:

 let s = "abcdefghi" let i = 2 print (s[s.index(s.startIndex, offsetBy:i)]) // print c 

对于使用string(范围)语法的string中的一系列字符(索引),我们可以使用下面的代码从第i个字符到第f个字符:

 let f = 6 print (s[s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 )]) //print cdefg 

对于来自使用String.substring(range)的string的子string(范围),我们可以使用下面的代码获取子string:

 print (s.substring (with:s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 ) ) ) //print cdefg 

笔记:

  1. 第i和第f从0开始。

  2. 到第f,我使用offsetBY:f + 1,因为订阅的范围使用.. <(半开运算符),不包括第f个位置。

  3. 当然,必须包括validation错误,如无效索引。

Swift 4扩展:

 extension String { subscript(_ range: CountableRange<Int>) -> String { let idx1 = index(startIndex, offsetBy: range.lowerBound) let idx2 = index(startIndex, offsetBy: range.upperBound) return String(self[idx1..<idx2]) } var count: Int { return characters.count } } 

用法:

 let s = "hello" s[0..<3] // "hel" s[3..<s.count] // "lo" 

或者unicode:

 let s = "😎🤣😋" s[0..<1] // "😎" 

同样的挫折,这不应该是那么难…

我编译了这个从较大文本中获取子字符位置的例子:

 // // Play with finding substrings returning an array of the non-unique words and positions in text // // import UIKit let Bigstring = "Why is it so hard to find substrings in Swift3" let searchStrs : Array<String>? = ["Why", "substrings", "Swift3"] FindSubString(inputStr: Bigstring, subStrings: searchStrs) func FindSubString(inputStr : String, subStrings: Array<String>?) -> Array<(String, Int, Int)> { var resultArray : Array<(String, Int, Int)> = [] for i: Int in 0...(subStrings?.count)!-1 { if inputStr.contains((subStrings?[i])!) { let range: Range<String.Index> = inputStr.range(of: subStrings![i])! let lPos = inputStr.distance(from: inputStr.startIndex, to: range.lowerBound) let uPos = inputStr.distance(from: inputStr.startIndex, to: range.upperBound) let element = ((subStrings?[i])! as String, lPos, uPos) resultArray.append(element) } } for words in resultArray { print(words) } return resultArray } 

返回(“为什么”,0,3)(“子串”,26,36)(“Swift3”,40,46)

这里有一个函数,当提供开始和结束索引时,返回给定子串的子串。 有关完整的参考,您可以访问下面给出的链接。

 func substring(string: String, fromIndex: Int, toIndex: Int) -> String? { if fromIndex < toIndex && toIndex < string.count /*use string.characters.count for swift3*/{ let startIndex = string.index(string.startIndex, offsetBy: fromIndex) let endIndex = string.index(string.startIndex, offsetBy: toIndex) return String(string[startIndex..<endIndex]) }else{ return nil } } 

这里是我创build的用于处理swift中的string操作的博客文章的链接。 swift中的string操作(也包括swift 4)

或者你可以在github上看到这个要点

我为此创build了一个简单的扩展(Swift 3)

 extension String { func substring(location: Int, length: Int) -> String? { guard characters.count >= location + length else { return nil } let start = index(startIndex, offsetBy: location) let end = index(startIndex, offsetBy: location + length) return substring(with: start..<end) } }