我一直在用 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>)
起初这些真的让我很困惑,所以我开始玩 index and range。这是子字符串的后续问题和答案。我在下面添加一个答案来展示它们是如何使用的。
https://i.stack.imgur.com/IKS4o.png
以下所有示例都使用
var str = "Hello, playground"
斯威夫特 4
字符串在 Swift 4 中进行了相当大的改进。现在,当您从 String 中获取一些子字符串时,您会得到一个 Substring
类型而不是 String
。为什么是这样?字符串是 Swift 中的值类型。这意味着如果您使用一个字符串来创建一个新字符串,则必须将其复制过来。这有利于稳定性(没有其他人会在你不知情的情况下更改它)但不利于效率。
另一方面,子字符串是对它所来自的原始字符串的引用。这是 documentation 中的一张图片说明了这一点。
无需复制,因此使用效率更高。但是,假设您从一百万个字符的字符串中得到了一个十字符的子字符串。因为 Substring 正在引用 String,所以只要 Substring 存在,系统就必须保留整个 String。因此,每当您完成对子字符串的操作时,将其转换为字符串。
let myString = String(mySubstring)
这将只复制子字符串,保存旧字符串的内存可以是 reclaimed。子字符串(作为一种类型)是短暂的。
Swift 4 的另一个重大改进是字符串是集合(再次)。这意味着您可以对集合执行任何操作,也可以对字符串执行任何操作(使用下标、迭代字符、过滤器等)。
以下示例展示了如何在 Swift 中获取子字符串。
获取子字符串
您可以使用下标或许多其他方法(例如,prefix
、suffix
、split
)从字符串中获取子字符串。不过,您仍然需要为范围使用 String.Index
而不是 Int
索引。 (如果您需要这方面的帮助,请参阅 my other answer。)
字符串的开头
您可以使用下标(注意 Swift 4 单边范围):
let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str[..<index] // Hello
或 prefix
:
let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str.prefix(upTo: index) // Hello
甚至更简单:
let mySubstring = str.prefix(5) // Hello
字符串的结尾
使用下标:
let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str[index...] // playground
或 suffix
:
let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str.suffix(from: index) // playground
甚至更简单:
let mySubstring = str.suffix(10) // playground
请注意,当使用 suffix(from: index)
时,我必须使用 -10
从末尾倒数。仅使用 suffix(x)
时没有必要这样做,它只接受字符串的最后 x
个字符。
字符串中的范围
我们再次在这里简单地使用下标。
let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let range = start..<end
let mySubstring = str[range] // play
将子字符串转换为字符串
不要忘记,当您准备好保存子字符串时,应将其转换为 String
,以便清理旧字符串的内存。
let myString = String(mySubstring)
使用 Int 索引扩展?
在阅读了 Airspeed Velocity 和 Ole Begemann 的文章 Strings in Swift 3 后,我犹豫是否要使用基于 Int
的索引扩展。尽管在 Swift 4 中,字符串是集合,但 Swift 团队故意没有使用 Int
索引。它仍然是 String.Index
。这与由不同数量的 Unicode 代码点组成的 Swift 字符有关。必须为每个字符串唯一地计算实际索引。
我不得不说,我希望 Swift 团队在未来能找到一种方法来抽象出 String.Index
。但在那之前,我选择使用他们的 API。它帮助我记住字符串操作不仅仅是简单的 Int
索引查找。
我对 Swift 的字符串访问模型感到非常沮丧:一切都必须是 Index
。我想要的只是使用 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
let str = "🇨🇭🇩🇪🇺🇸Hello"
print(str.substring(to: 2))
str[5]
,我想访问索引 5 处的字符,无论该字符看起来是什么或它需要多少字节。 Swift 不就是为了提高开发人员的生产力吗?
countElement(str)
来查找长度。在 Swift 3 中,Apple 制作了不符合 Sequence
的字符串,并强制所有人改用 str.characters
。这些家伙不怕做出改变。他们对整数下标的固执真的很难理解
Swift 5 扩展:
extension String {
subscript(_ range: CountableRange<Int>) -> String {
let start = index(startIndex, offsetBy: max(0, range.lowerBound))
let end = index(start, offsetBy: min(self.count - range.lowerBound,
range.upperBound - range.lowerBound))
return String(self[start..<end])
}
subscript(_ range: CountablePartialRangeFrom<Int>) -> String {
let start = index(startIndex, offsetBy: max(0, range.lowerBound))
return String(self[start...])
}
}
用法:
let s = "hello"
s[0..<3] // "hel"
s[3...] // "lo"
或 unicode:
let s = "😎🤣😋"
s[0..<1] // "😎"
count
仅在 self.characters
上可用
s[0...2]
,您还需要添加 an extension that takes a CountableClosedRange<Int>
。
CountablePartialRangeFrom<Int>
为 s[2...]。
斯威夫特 4 和 5:
extension String {
subscript(_ i: Int) -> String {
let idx1 = index(startIndex, offsetBy: i)
let idx2 = index(idx1, offsetBy: 1)
return String(self[idx1..<idx2])
}
subscript (r: Range<Int>) -> String {
let start = index(startIndex, offsetBy: r.lowerBound)
let end = index(startIndex, offsetBy: r.upperBound)
return String(self[start ..< end])
}
subscript (r: CountableClosedRange<Int>) -> String {
let startIndex = self.index(self.startIndex, offsetBy: r.lowerBound)
let endIndex = self.index(startIndex, offsetBy: r.upperBound - r.lowerBound)
return String(self[startIndex...endIndex])
}
}
如何使用它:
"abcde"[0] --> "a" "abcde"[0...2] --> "abc" "abcde"[2..<4] --> "cd"
斯威夫特 4
在 swift 4 中,String
符合 Collection
。我们现在应该使用 subscript.
而不是 substring
。因此,如果您只想从 "Hello, playground"
中删除单词 "play"
,您可以这样做:
var str = "Hello, playground"
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
与原始字符串共享其存储空间。但是以这种方式共享内存也很容易导致内存泄漏。
这就是为什么你应该将结果复制到一个新的字符串中,一旦你想清理原始字符串。你可以使用普通的构造函数来做到这一点:
let newString = String(result)
您可以在 [Apple 文档] 中找到有关新 Substring
类的更多信息。1
因此,例如,如果您得到 Range
作为 NSRegularExpression
的结果,您可以使用以下扩展:
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)
}
}
text[Range( nsRange , in: text)!]
遇到了这种实现这一目标的相当简短和简单的方法。
var str = "Hello, World"
let arrStr = Array(str)
print(arrStr[0..<5]) //["H", "e", "l", "l", "o"]
print(arrStr[7..<12]) //["W", "o", "r", "l", "d"]
print(String(arrStr[0..<5])) //Hello
print(String(arrStr[7..<12])) //World
这是一个在提供开始和结束索引时返回给定子字符串的子字符串的函数。如需完整参考,您可以访问下面给出的链接。
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
}
}
这是我为快速处理字符串操作而创建的博客文章的链接。 String manipulation in swift (Covers swift 4 as well)
Or you can see this gist on github
我有同样的最初反应。我也对每个主要版本的语法和对象变化如此之大感到沮丧。
然而,我从经验中意识到,我最终总是会遭受试图与“改变”作斗争的后果,比如处理多字节字符,如果你看的是全球观众,这是不可避免的。
因此,我决定认可并尊重 Apple 工程师所做的努力,并通过了解他们提出这种“可怕”方法时的心态来尽我所能。
与其创建扩展,这只是一种让您的生活更轻松的解决方法(我并不是说它们是错误的或昂贵的),不如弄清楚字符串现在是如何设计的。
例如,我有这段代码在 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)
在放弃尝试使用相同的方法(例如使用子字符串)之后,我终于理解了将字符串视为双向集合的概念,我最终得到了相同代码的这个版本:
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))
我希望这有助于...
我的思维很机械。这里是基本...
斯威夫特 4 斯威夫特 5
let t = "abracadabra"
let start1 = t.index(t.startIndex, offsetBy:0)
let end1 = t.index(t.endIndex, offsetBy:-5)
let start2 = t.index(t.endIndex, offsetBy:-5)
let end2 = t.index(t.endIndex, offsetBy:0)
let t2 = t[start1 ..< end1]
let t3 = t[start2 ..< end2]
//or a shorter form
let t4 = t[..<end1]
let t5 = t[start2...]
print("\(t2) \(t3) \(t)")
print("\(t4) \(t5) \(t)")
// result:
// abraca dabra abracadabra
结果是一个子字符串,这意味着它是原始字符串的一部分。要获得完整的单独字符串,只需使用例如
String(t3)
String(t4)
这就是我使用的:
let mid = t.index(t.endIndex, offsetBy:-5)
let firstHalf = t[..<mid]
let secondHalf = t[mid...]
我是 Swift 3 的新手,但是看一下 String
(索引)语法进行类比,我认为索引就像一个限制为字符串的“指针”,而 Int 可以作为一个独立的对象提供帮助。使用 base + offset 语法,然后我们可以使用下面的代码从字符串中获取第 i 个字符:
let s = "abcdefghi"
let i = 2
print (s[s.index(s.startIndex, offsetBy:i)])
// print c
对于使用字符串(范围)语法的字符串中的一系列字符(索引),我们可以使用下面的代码获得第 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) 的字符串中的子字符串(范围),我们可以使用以下代码获取子字符串:
print (s.substring (with:s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 ) ) )
//print cdefg
笔记:
第i个和第f个以0开头。到第f个,我使用offsetBY:f + 1,因为订阅范围使用..<(半开运算符),不包括第f个位置。当然必须包括无效索引之类的验证错误。
同样的挫败感,这不应该那么难......
我编译了这个从较大文本中获取子字符串位置的示例:
//
// 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
}
返回 ("Why", 0, 3) ("substrings", 26, 36) ("Swift3", 40, 46)
斯威夫特 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
斯威夫特 4
extension String {
subscript(_ i: Int) -> String {
let idx1 = index(startIndex, offsetBy: i)
let idx2 = index(idx1, offsetBy: 1)
return String(self[idx1..<idx2])
}
}
let s = "hello"
s[0] // h
s[1] // e
s[2] // l
s[3] // l
s[4] // o
我为此创建了一个简单的扩展(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)
}
}
这是一个更通用的实现:
这种技术仍然使用 index
来保持 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)
}
}
从第三个字符开始子字符串:
let item = "Fred looks funny"
item.subString(item.index(at: 2)...) // "ed looks funny"
我使用骆驼 subString
表示它返回 String
而不是 Substring
。
在上面的基础上,我需要在一个非打印字符处拆分一个字符串,删除非打印字符。我开发了两种方法:
var str = "abc\u{1A}12345sdf"
let range1: Range<String.Index> = str.range(of: "\u{1A}")!
let index1: Int = str.distance(from: str.startIndex, to: range1.lowerBound)
let start = str.index(str.startIndex, offsetBy: index1)
let end = str.index(str.endIndex, offsetBy: -0)
let result = str[start..<end] // The result is of type Substring
let firstStr = str[str.startIndex..<range1.lowerBound]
我使用上面的一些答案放在一起。
因为 String 是一个集合,所以我做了以下操作:
var fString = String()
for (n,c) in str.enumerated(){
*if c == "\u{1A}" {
print(fString);
let lString = str.dropFirst(n + 1)
print(lString)
break
}
fString += String(c)
}*
这对我来说更直观。哪个最好?我无法告诉他们他们都使用 Swift 5
var str = "VEGANISM"
print (str[str.index(str.startIndex, offsetBy:2)..<str.index(str.endIndex, offsetBy: -1)] )
//Output-> GANIS
这里,str.startIndex
和 str.endIndex
是字符串的起始索引和结束索引。
这里作为 startIndex = 2 -> 中的 offsetBy str.index(str.startIndex, offsetBy:2)
因此,修剪后的字符串将从索引 2 开始(即从第二个字符开始)和 endIndex = -1 -> 中的 offsetBy str.index(str.endIndex, offsetBy: -1)
即从末尾修剪 1 个字符。
var str = "VEGANISM"
print (str[str.index(str.startIndex, offsetBy:0)..<str.index(str.endIndex, offsetBy: 0)] )
//Output-> VEGANISM
由于两边都有 offsetBy value = 0
,即 str.index(str.startIndex, offsetBy:0)
和 str.index(str.endIndex, offsetBy: 0)
,因此正在打印完整的字符串
我创建了一个这样的简单函数:
func sliceString(str: String, start: Int, end: Int) -> String {
let data = Array(str)
return String(data[start..<end])
}
您可以通过以下方式使用它
print(sliceString(str: "0123456789", start: 0, end: 3)) // -> prints 012
斯威夫特 4
“子字符串”(https://developer.apple.com/documentation/swift/substring):
let greeting = "Hi there! It's nice to meet you! 👋"
let endOfSentence = greeting.index(of: "!")!
let firstSentence = greeting[...endOfSentence]
// firstSentence == "Hi there!"
扩展字符串示例:
private typealias HowDoYouLikeThatElonMusk = String
private extension HowDoYouLikeThatElonMusk {
subscript(_ from: Character?, _ to: Character?, _ include: Bool) -> String? {
if let _from: Character = from, let _to: Character = to {
let dynamicSourceForEnd: String = (_from == _to ? String(self.reversed()) : self)
guard let startOfSentence: String.Index = self.index(of: _from),
let endOfSentence: String.Index = dynamicSourceForEnd.index(of: _to) else {
return nil
}
let result: String = String(self[startOfSentence...endOfSentence])
if include == false {
guard result.count > 2 else {
return nil
}
return String(result[result.index(result.startIndex, offsetBy: 1)..<result.index(result.endIndex, offsetBy: -1)])
}
return result
} else if let _from: Character = from {
guard let startOfSentence: String.Index = self.index(of: _from) else {
return nil
}
let result: String = String(self[startOfSentence...])
if include == false {
guard result.count > 1 else {
return nil
}
return String(result[result.index(result.startIndex, offsetBy: 1)...])
}
return result
} else if let _to: Character = to {
guard let endOfSentence: String.Index = self.index(of: _to) else {
return nil
}
let result: String = String(self[...endOfSentence])
if include == false {
guard result.count > 1 else {
return nil
}
return String(result[..<result.index(result.endIndex, offsetBy: -1)])
}
return result
}
return nil
}
}
使用扩展字符串的示例:
let source = ">>>01234..56789<<<"
// include = true
var from = source["3", nil, true] // "34..56789<<<"
var to = source[nil, "6", true] // ">>>01234..56"
var fromTo = source["3", "6", true] // "34..56"
let notFound = source["a", nil, true] // nil
// include = false
from = source["3", nil, false] // "4..56789<<<"
to = source[nil, "6", false] // ">>>01234..5"
fromTo = source["3", "6", false] // "4..5"
let outOfBounds = source[".", ".", false] // nil
let str = "Hello, playground"
let hello = str[nil, ",", false] // "Hello"
String
的特殊性主要在其他答案中得到解决。解释一下:String
有一个特定的 Index
,它不是 Int
类型,因为在一般情况下,字符串元素的大小不同。因此,String
不符合 RandomAccessCollection
并且访问特定索引意味着遍历集合,这不是 O(1) 操作。
许多答案都提出了使用范围的解决方法,但它们可能导致代码效率低下,因为它们使用不是 O(1) 的字符串方法(index(from:)
、index(:offsetBy:)
、...)。
要访问数组中的字符串元素,您应该使用 Array
:
let array = Array("Hello, world!")
let letter = array[5]
这是一个权衡,数组创建是 O(n) 操作,但数组访问是 O(1)。您可以在需要时使用 String(array)
转换回字符串。
斯威夫特 5
// 想象一下,需要从 2 生成子字符串,长度为 3
let s = "abcdef"
let subs = s.suffix(s.count-2).prefix(3)
// 现在 subs = "cde"
Swift 5
let desiredIndex: Int = 7 let substring = str[String.Index(encodedOffset: desiredIndex)...]
这个子字符串变量会给你结果。
这里简单地将 Int 转换为 Index ,然后您可以拆分字符串。除非你会出错。
garbage collected
;-) 我希望这里的人们知道 Swift 中没有垃圾收集。