字符串子字符串如何在Swift工作

我一直在更新我的一些旧代码和答案与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>)

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

481418 次浏览

enter image description here

下面的所有示例都使用

var str = "Hello, playground"

斯威夫特4

在Swift 4中,字符串进行了相当大的修改。当你从String对象中获取子字符串时,你会得到一个Substring类型,而不是String类型。为什么会这样?字符串是Swift中的值类型。这意味着如果你使用一个字符串来创建一个新的字符串,那么它必须被复制。这有利于稳定性(没有人会在你不知情的情况下改变它),但不利于效率。

另一方面,Substring是返回到它所来自的原始String的引用。下面是一张来自文档的图像,说明了这一点。

不需要复制,所以使用起来更有效率。但是,假设您从100万个字符字符串中获得了10个字符的子字符串。因为Substring引用了String,只要Substring存在,系统就必须保留整个String。因此,当你完成对Substring的操作时,将其转换为String。

let myString = String(mySubstring)

这将只复制子字符串,保存旧字符串的内存可以是回收。子字符串(作为一种类型)意味着生命周期很短。

Swift 4的另一个重大改进是字符串是集合(再次)。这意味着你可以对Collection做什么,也可以对String做什么(使用下标、遍历字符、过滤器等)。

下面的例子展示了如何在Swift中获取子字符串。

获得子字符串

你可以通过使用下标或许多其他方法(例如,prefixsuffixsplit)从字符串中获得子字符串。你仍然需要使用String.Index索引,而不是Int索引。(如果你需要帮助,请参阅我的另一个答案。)

字符串的开头

你可以使用下标(注意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)时,这是不必要的,它只接受String的最后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

Substring转换为String

不要忘记,当你准备保存你的子字符串时,你应该将它转换为String,以便旧字符串的内存可以被清理。

let myString = String(mySubstring)

使用Int索引扩展?

在阅读了Airspeed Velocity和Ole Begemann的文章 Swift 3中的字符串后,我犹豫是否使用基于Int的索引扩展。虽然在Swift 4中,字符串是集合,但Swift团队故意没有使用Int索引。它仍然是String.Index。这与Swift字符由不同数量的Unicode码点组成有关。实际索引必须为每个字符串唯一地计算。

我不得不说,我希望Swift团队能在未来找到一种抽象String.Index的方法。但在那之前,我选择使用他们的API。它帮助我记住String操作不仅仅是简单的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

我最初的反应也一样。我也对语法和对象在每个主要版本中发生如此巨大的变化感到沮丧。

然而,我从经验中意识到,我总是在努力对抗“变化”的过程中最终遭受后果,比如处理多字节字符,如果你面对的是全球受众,这是不可避免的。

因此,我决定承认并尊重苹果工程师所付出的努力,并尽我所能,理解他们在想出这种“可怕”方法时的心态。

与其创建扩展,这只是一个让你的生活更容易的解决方案(我不是说他们是错误的或昂贵的),为什么不弄清楚字符串现在是如何设计工作的。

例如,我在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)

在放弃尝试使用相同的方法工作后,例如使用Substrings,我终于理解了将字符串作为双向集合的概念,为此我最终得到了这个版本的相同代码:

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))

我希望这有助于……

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

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

//
// 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
}
< p >返回 ("Why" 0,3) ("substrings", 26, 36) (“Swift3”,40,46)

我是Swift 3的新手,但看String(索引)语法类比,我认为索引就像一个“指针”约束到字符串和Int可以帮助作为一个独立的对象。使用base + offset语法,然后我们可以用下面的代码从string中获得第i个字符:

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

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

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

对于字符串中的子字符串(range),使用string。Substring (range),我们可以使用下面的代码获得子字符串:

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-th,我使用offsetBY: f + 1,因为订阅范围使用..<(半开运算符),不包括第f个位置。

  3. 当然必须包括无效索引等验证错误。

我为此创建了一个简单的扩展(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)
}
}

下面是一个函数,当提供了开始和结束索引时,返回给定子字符串的子字符串。如需完整参考资料,请浏览以下连结。

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
}
}
这是我创建的博客文章的链接,用于处理swift中的字符串操作。 swift中的字符串操作(也包括swift 4) < / p >

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

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] // "😎"

斯威夫特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与原始String共享其存储空间。然而,以这种方式共享内存也很容易导致内存泄漏。

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

let newString = String(result)

你可以在[Apple文档].1中找到更多关于新Substring类的信息

因此,例如,如果你得到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)
}


}

斯威夫特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

斯威夫特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!"

扩展名为String的例子:

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
}
}

使用扩展名String的例子:

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"

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

这种技术仍然使用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)
}
}

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

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]

我用上面的一些答案组合在一起。

因为字符串是一个集合,然后我做了以下工作:

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一起工作

我的思维很机械。以下是一些基本常识……

< >强劲迅速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

结果是一个子字符串,这意味着它是原始字符串的一部分。要得到一个完整的独立字符串,只需使用e.g.。

    String(t3)
String(t4)

这是我所使用的:

    let mid = t.index(t.endIndex, offsetBy:-5)
let firstHalf = t[..<mid]
let secondHalf = t[mid...]

斯威夫特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 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 . . & lt; 4)——比;“cd"

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

我发现了这个相当简单的方法。

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

String的特殊性已经在其他答案中得到了解决。解释一下:String有一个特定的Index,它不是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(array)转换回字符串。

var str = "VEGANISM"
print (str[str.index(str.startIndex, offsetBy:2)..<str.index(str.endIndex, offsetBy: -1)] )


//Output-> GANIS

这里,str.startIndexstr.endIndex是字符串的开始索引和结束索引。

这里是startIndex = 2 ->str.index(str.startIndex, offsetBy:2)因此修剪后的字符串将从索引2开始(即从第二个字符开始)和offsetBy in endIndex = -1 ->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

斯威夫特5

//假设,需要从2创建子字符串,长度为3

let s = "abcdef"
let subs = s.suffix(s.count-2).prefix(3)

//现在subs = "cde"

Swift 5解决方案高性能

let fromIndex = s.index(s.startIndex, offsetBy: fromIndex)
let toIndex = s.index(s.startIndex, offsetBy: toIndex)

我使用这种方法从一个Leetcode问题的fromIndextoIndex的子字符串,它超时了,看起来这是非常低效和缓慢的,并导致了超时。

一个更快的纯Swift方式来完成这个是:

let fromIndex = String.Index(utf16Offset:fromIndex, in: s)
let toIndex = String.Index(utf16Offset: toIndex, in: s)