String子串如何在Swift中工作

Sur*_*gch 316 string substring range swift

我一直在使用Swift 3更新我的一些旧代码和答案但是当我使用子字符串获取Swift字符串和索引时,事情变得令人困惑.

具体来说,我尝试以下方法:

let str = "Hello, playground"
let prefixRange = str.startIndex..<str.startIndex.advancedBy(5)
let prefix = str.substringWithRange(prefixRange)
Run Code Online (Sandbox Code Playgroud)

第二行给我以下错误

'String'类型的值没有成员'substringWithRange'

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

str.substring(to: String.Index)
str.substring(from: String.Index)
str.substring(with: Range<String.Index>)
Run Code Online (Sandbox Code Playgroud)

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

Sur*_*gch 753

在此输入图像描述

以下所有示例均使用

var str = "Hello, playground"
Run Code Online (Sandbox Code Playgroud)

斯威夫特4

Strings在Swift 4中得到了相当大的改革.当你从String获得一些子串时,你会得到一个Substring类型而不是一个类型String.为什么是这样?字符串是Swift中的值类型.这意味着如果您使用一个String来创建一个String,则必须将其复制.这对稳定性有好处(没有其他人会在你不知情的情况下改变它)但对效率不利.

另一方面,Substring是一个返回原始String的引用.这是来自说明该文档的文档中的图像.

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

let myString = String(mySubstring)
Run Code Online (Sandbox Code Playgroud)

这将只复制子串,旧的String可以被垃圾收集.子串(作为一种类型)意味着短暂的.

Swift 4的另一个重大改进是Strings是Collections(再次).这意味着无论你对集合做什么,你都可以做一个String(使用下标,迭代字符,过滤等).

以下示例显示如何在Swift中获取子字符串.

获得子串

您可以使用标或一些其他的方法获得一个字符串的子串(例如prefix,suffix,split).但是,您仍然需要使用String.Index而不是Int该范围的索引.(如果你需要帮助,请参阅我的其他答案.)

字符串的开头

你可以使用下标(注意Swift 4单侧范围):

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str[..<index] // Hello
Run Code Online (Sandbox Code Playgroud)

或者prefix:

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str.prefix(upTo: index) // Hello
Run Code Online (Sandbox Code Playgroud)

甚至更容易:

let mySubstring = str.prefix(5) // Hello
Run Code Online (Sandbox Code Playgroud)

一个字符串的结尾

使用下标:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str[index...] // playground
Run Code Online (Sandbox Code Playgroud)

或者suffix:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str.suffix(from: index) // playground
Run Code Online (Sandbox Code Playgroud)

甚至更容易:

let mySubstring = str.suffix(10) // playground
Run Code Online (Sandbox Code Playgroud)

请注意,使用时suffix(from: index)我必须使用从头到尾倒数-10.只使用时suffix(x),这不是必需的,它只占用xString 的最后一个字符.

字符串中的范围

我们再次在这里使用下标.

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
Run Code Online (Sandbox Code Playgroud)

转换SubstringString

不要忘记,当您准备保存子字符串时,您应该将其转换为a,String以便可以清除旧字符串的内存.

let myString = String(mySubstring)
Run Code Online (Sandbox Code Playgroud)

使用Int索引扩展?

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

我不得不说,我希望Swift团队能够String.Index在未来找到一种方法.但在他们之前,我选择使用他们的API.它帮助我记住String操作不仅仅是简单的Int索引查找.

  • Swift中几乎所有东西都过于复杂.类型安全和强制错误处理只会导致带有标点符号的丑陋代码,最终难以阅读并且无法防止错误代码和相关异常.它的微小速度和效率改进与99%的应用程序无关.斯威夫特是新皇帝,很少有人注意到其实是赤裸裸的 (59认同)
  • 苹果真的把字符串搞得一团糟。他们不应该在 Swift 版本之间不断更改它们,并且您不应该创建扩展来执行 substring() 等简单的操作。这应该内置到语言中。 (5认同)
  • 对于desctription来说.当之无愧的高涨.苹果过于复杂.子串应该像string.substring [from ... to]一样简单. (4认同)

Cod*_*ent 164

我对Swift的String访问模型感到非常沮丧:一切都必须是Index.我想要的只是访问字符串的第i个字符Int,而不是笨拙的索引和推进(这恰好随着每个主要版本而变化).所以我做了一个扩展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
Run Code Online (Sandbox Code Playgroud)

  • 是的,我知道一个字符(即*扩展字形集群*)可以占用多个字节.我很沮丧的是为什么我们必须使用详细的索引推进方法来访问字符串的字符.为什么Swift团队不能只为核心库添加一些重载来抽象它.如果我键入`str [5]`,我想访问索引5处的字符,无论该字符是什么字符或者需要多少字节.Swift不是关于开发人员的生产力吗? (88认同)
  • 索引非常有用,因为*字符*可以超过一个字节.试试`let str ="Hello"``print(str.substring(to:2))` (4认同)
  • @RenniePet我相信Apple认识到这个问题并且即将发生变化.根据GitHub上的Swift Evolution页面:"Swift 4旨在使字符串更强大,更易于使用,同时默认保留Unicode正确性".这很模糊,但让我们保持希望 (4认同)
  • @RaimundasSakalauskas这个说法不是我的意思.C#具有Unicode正确性和整数下标,这非常方便.在Swift 1中,Apple希望开发人员使用`countElement(str)`来查找长度.在Swift 3中,Apple使字符串不符合`Sequence`并强迫所有人使用`str.characters`代替.这些家伙不怕改变.他们对整数下标的固执很难理解 (4认同)
  • @CodeDifferent 为什么苹果没有添加下标字符访问?让人们明白这样做是不好的。基本上,如果您使用双循环下标在 0..string.count 中为 i 执行操作,则底层索引必须遍历字符串的每个字节以找出下一个字符。如果使用索引循环,则只对字符串迭代一次。顺便说一句,我自己讨厌这个,但这就是下标在 swift 中的字符串上不可用的原因。 (3认同)
  • @vadian,您的示例字符串是 [Unicode 和 Swift 本身的问题](http://stackoverflow.com/q/41608512/),而不是作者扩展名的问题。然而,当试图在扩展中抽象出复杂性时,还有 [其他需要考虑的问题](https://oleb.net/blog/2016/08/swift-3-strings/)。 (2认同)
  • Swift 不仅不迅速,而且在字符串方面也不聪明。这太疯狂了,没有任何方法可以找到字符串中子字符串的索引!苹果,你还好吗? (2认同)

Lou*_*ell 73

Swift 4扩展:

extension String { 
    subscript(_ range: CountableRange<Int>) -> String { 
        let idx1 = index(startIndex, offsetBy: max(0, range.lowerBound))
        let idx2 = index(startIndex, offsetBy: min(self.count, range.upperBound))
        return String(self[idx1..<idx2])
    }    
}       
Run Code Online (Sandbox Code Playgroud)

用法:

let s = "hello"
s[0..<3] // "hel"
s[3..<s.count] // "lo"
Run Code Online (Sandbox Code Playgroud)

或unicode:

let s = ""
s[0..<1] // ""
Run Code Online (Sandbox Code Playgroud)

  • 好多了,感谢您发布此扩展程序!我认为来自 Python 的 Swift 比习惯要难得多。对于从 Objective C 转向 Swift 的另一个方向的人来说,似乎有更积极的确认。 (2认同)
  • 这个特定的扩展有什么需要注意的问题吗?为什么苹果不做这样的事情呢? (2认同)
  • 如果您想编写例如`s [0 ...,请添加[需要`CountableClosedRange &lt;Int&gt;``的扩展名“](/sf/answers/3263926921/)。 2]`。 (2认同)
  • @ChrisFrederick 和 s[2...] 的 CountablePartialRangeFrom&lt;Int&gt;`。 (2认同)

geb*_*bel 19

斯威夫特4

在swift 4 String符合Collection.相反的substring,我们现在应该使用subscript.,如果你想切出只有两个字所以"play""Hello, playground",你可以做这样的:

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
Run Code Online (Sandbox Code Playgroud)

有趣的是,这样做会给你一个Substring而不是一个String.这是快速有效的,因为Substring它与原始String共享其存储.但是,以这种方式共享内存也很容易导致内存泄漏.

这就是为什么要在清理原始String时将结果复制到新String中的原因.您可以使用普通构造函数执行此操作:

let newString = String(result)
Run Code Online (Sandbox Code Playgroud)

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

因此,如果您获得a 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)
    }

}
Run Code Online (Sandbox Code Playgroud)


Sou*_*HDI 15

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])
  }
}
Run Code Online (Sandbox Code Playgroud)

如何使用它:

“ abcde” [0]->“ a”

“ abcde” [0 ... 2]->“ abc”

“ abcde” [2 .. <4]->“ cd”


Mah*_*ava 9

遇到了这种相当短而简单的方法来实现这一目标。

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
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,天啊,Apple 能让子字符串变得更复杂吗?!这可能不是性能最好的解决方案,但在我的情况下,代码几乎不会被调用,因此这是可读性的一个很好的折衷方案。 (2认同)

t1s*_*ser 8

我的思维很机械。以下是基础知识...

斯威夫特 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
Run Code Online (Sandbox Code Playgroud)

结果是一个子字符串,这意味着它是原始字符串的一部分。要获得完整的单独字符串,只需使用例如

    String(t3)
    String(t4)
Run Code Online (Sandbox Code Playgroud)

这是我使用的:

    let mid = t.index(t.endIndex, offsetBy:-5)
    let firstHalf = t[..<mid]
    let secondHalf = t[mid...]
Run Code Online (Sandbox Code Playgroud)


小智 7

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

然而,我从经验中意识到,我总是最终会遭遇试图打击"改变"的后果,就像处理多字节字符一样,如果你正在关注全球观众,这是不可避免的.

因此,我决定承认并尊重Apple工程师所做的努力,并在他们提出这种"恐怖"方法时理解他们的心态.

而不是创建扩展只是一种变通方法,以使您的生活更轻松(我不是说他们错了或昂贵),为什么不弄清楚Strings现在如何设计工作.

例如,我有这个代码正在使用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)
Run Code Online (Sandbox Code Playgroud)

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

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))
Run Code Online (Sandbox Code Playgroud)

我希望这有助于......


Nik*_*Jha 7

这是一个函数,它在提供开始和结束索引时返回给定子字符串的子字符串.如需完整参考,请访问以下链接.

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
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我创建的博客文章的链接,用于处理swift中的字符串操作. swift中的字符串操作(也包括swift 4)

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


小智 7

雨燕5

// 想象一下,需要从 2 中生成子字符串,长度为 3

let s = "abcdef"    
let subs = s.suffix(s.count-2).prefix(3) 
Run Code Online (Sandbox Code Playgroud)

// 现在 subs = "cde"


Seu*_*jun 6

我创建了一个像这样的简单函数:

func sliceString(str: String, start: Int, end: Int) -> String {
    let data = Array(str)
    return String(data[start..<end])
}
Run Code Online (Sandbox Code Playgroud)

你可以通过以下方式使用它

print(sliceString(str: "0123456789", start: 0, end: 3)) // -> prints 012
Run Code Online (Sandbox Code Playgroud)


Tal*_*ane 5

同样的挫败感,这应该没那么难……

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

//
// 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
}
Run Code Online (Sandbox Code Playgroud)

返回 ("Why", 0, 3) ("substrings", 26, 36) ("Swift3", 40, 46)

  • 这是一些代码,但并没有真正解释字符串索引和子字符串在 swift3 中是如何工作的。 (3认同)

小智 5

我是Swift 3中的新手,但是看起来String类比的(索引)语法我认为索引就像一个约束到字符串的"指针",而Int可以作为一个独立的对象.使用base + offset语法,然后我们可以从字符串中获取第i个字符,代码如下:

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

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

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

对于使用String.substring(range)的字符串的子字符串(范围),我们可以使用以下代码获取子字符串:

print (s.substring (with:s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 ) ) )
//print cdefg
Run Code Online (Sandbox Code Playgroud)

笔记:

  1. 第i和第f开始于0.

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

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


Pet*_*inz 5

斯威夫特 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])
    }
}
Run Code Online (Sandbox Code Playgroud)

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

例子:

let text = "Hello, World!"
let substring = text.take(5) //Hello
Run Code Online (Sandbox Code Playgroud)