Swift: 如何获得从开始到最后一个字符索引的子字符串

我想学习将一个字符串转换成另一个字符串的最佳/最简单的方法,但是只有一个子集,从开始到字符的最后一个索引。

例如,将“ www.stackoverflow.com”转换为“ www.stackoverflow”。什么样的代码片段可以做到这一点,而且是最快的?(我希望这不会引起争论,但是我找不到关于如何在 Swift 中处理子字符串的好教训。

159770 次浏览

我是这样做的。你可以用同样的方法来做,或者用这个代码来表达你的想法。

let s = "www.stackoverflow.com"
s.substringWithRange(0..<s.lastIndexOf("."))

Here are the extensions I use:

import Foundation
extension String {


var length: Int {
get {
return countElements(self)
}
}


func indexOf(target: String) -> Int {
var range = self.rangeOfString(target)
if let range = range {
return distance(self.startIndex, range.startIndex)
} else {
return -1
}
}


func indexOf(target: String, startIndex: Int) -> Int {
var startRange = advance(self.startIndex, startIndex)
var range = self.rangeOfString(target, options: NSStringCompareOptions.LiteralSearch, range: Range<String.Index>(start: startRange, end: self.endIndex))
if let range = range {
return distance(self.startIndex, range.startIndex)
} else {
return -1
}
}


func lastIndexOf(target: String) -> Int {
var index = -1
var stepIndex = self.indexOf(target)
while stepIndex > -1 {
index = stepIndex
if stepIndex + target.length < self.length {
stepIndex = indexOf(target, startIndex: stepIndex + target.length)
} else {
stepIndex = -1
}
}
return index
}


func substringWithRange(range:Range<Int>) -> String {
let start = advance(self.startIndex, range.startIndex)
let end = advance(self.startIndex, range.endIndex)
return self.substringWithRange(start..<end)
}


}

信贷 Albertbori/Common Swift String 扩展

一般来说,我是扩展的坚定支持者,特别是对于像字符串操作、搜索和切片这样的需求。

只是反向访问

最好的方法是将 substringToIndexendIndex属性和 advance全局函数结合使用。

var string1 = "www.stackoverflow.com"


var index1 = advance(string1.endIndex, -4)


var substring1 = string1.substringToIndex(index1)

从后面开始找绳子

使用 rangeOfString并将 options设置为 .BackwardsSearch

var string2 = "www.stackoverflow.com"


var index2 = string2.rangeOfString(".", options: .BackwardsSearch)?.startIndex


var substring2 = string2.substringToIndex(index2!)

没有延期,完全是斯威夫特的风格

Swift 2.0

advance现在是 Index的一部分,叫做 advancedBy。你可以这样做:

var string1 = "www.stackoverflow.com"


var index1 = string1.endIndex.advancedBy(-4)


var substring1 = string1.substringToIndex(index1)

Swift 3.0

You can't call advancedBy on a String because it has variable size elements. You have to use index(_, offsetBy:).

var string1 = "www.stackoverflow.com"


var index1 = string1.index(string1.endIndex, offsetBy: -4)


var substring1 = string1.substring(to: index1)

很多东西都被重命名了,案例都是用 camelCase 写的,startIndex变成了 lowerBound

var string2 = "www.stackoverflow.com"


var index2 = string2.range(of: ".", options: .backwards)?.lowerBound


var substring2 = string2.substring(to: index2!)

另外,我不建议强制拆封 index2。你可以使用可选的绑定或 map。就个人而言,我更喜欢使用 map:

var substring3 = index2.map(string2.substring(to:))

Swift 4

Swift 3版本仍然有效,但是现在你可以使用带有索引范围的下标:

let string1 = "www.stackoverflow.com"


let index1 = string1.index(string1.endIndex, offsetBy: -4)


let substring1 = string1[..<index1]

第二种方法保持不变:

let string2 = "www.stackoverflow.com"


let index2 = string2.range(of: ".", options: .backwards)?.lowerBound


let substring3 = index2.map(string2.substring(to:))

String拥有内置子串特性:

extension String : Sliceable {
subscript (subRange: Range<String.Index>) -> String { get }
}

如果您想要的是“转到字符的 第一索引”,您可以使用内置的 find()函数获得子字符串:

var str = "www.stackexchange.com"
str[str.startIndex ..< find(str, ".")!] // -> "www"

要查找 最后索引,我们可以实现 findLast()

/// Returns the last index where `value` appears in `domain` or `nil` if
/// `value` is not found.
///
/// Complexity: O(\ `countElements(domain)`\ )
func findLast<C: CollectionType where C.Generator.Element: Equatable>(domain: C, value: C.Generator.Element) -> C.Index? {
var last:C.Index? = nil
for i in domain.startIndex..<domain.endIndex {
if domain[i] == value {
last = i
}
}
return last
}


let str = "www.stackexchange.com"
let substring = map(findLast(str, ".")) { str[str.startIndex ..< $0] } // as String?
// if "." is found, substring has some, otherwise `nil`

ADDED:

也许,BidirectionalIndexType专用版的 findLast更快:

func findLast<C: CollectionType where C.Generator.Element: Equatable, C.Index: BidirectionalIndexType>(domain: C, value: C.Generator.Element) -> C.Index? {
for i in lazy(domain.startIndex ..< domain.endIndex).reverse() {
if domain[i] == value {
return i
}
}
return nil
}

edit/update:

Swift 4 or later(Xcode 10.0 +)中,您可以使用新的 < a href = “ https://developer.apple.com/document/swift/bidirectionalCollection”rel = “ nofollow noReferrer”> BidirectionalCollection 方法 < a href = “ https://developer.apple.com/document/swift/bidirectionalCollection/2994840-lastIndex”rel = “ nofollow norefrer”> lastIndex (of:)

func lastIndex(of element: Self.Element) -> Self.Index?

let string = "www.stackoverflow.com"
if let lastIndex = string.lastIndex(of: ".") {
let subString = string[..<lastIndex]  // "www.stackoverflow"
}

You can use these extensions:

Swift 2.3

 extension String
{
func substringFromIndex(index: Int) -> String
{
if (index < 0 || index > self.characters.count)
{
print("index \(index) out of bounds")
return ""
}
return self.substringFromIndex(self.startIndex.advancedBy(index))
}


func substringToIndex(index: Int) -> String
{
if (index < 0 || index > self.characters.count)
{
print("index \(index) out of bounds")
return ""
}
return self.substringToIndex(self.startIndex.advancedBy(index))
}


func substringWithRange(start: Int, end: Int) -> String
{
if (start < 0 || start > self.characters.count)
{
print("start index \(start) out of bounds")
return ""
}
else if end < 0 || end > self.characters.count
{
print("end index \(end) out of bounds")
return ""
}
let range = Range(start: self.startIndex.advancedBy(start), end: self.startIndex.advancedBy(end))
return self.substringWithRange(range)
}


func substringWithRange(start: Int, location: Int) -> String
{
if (start < 0 || start > self.characters.count)
{
print("start index \(start) out of bounds")
return ""
}
else if location < 0 || start + location > self.characters.count
{
print("end index \(start + location) out of bounds")
return ""
}
let range = Range(start: self.startIndex.advancedBy(start), end: self.startIndex.advancedBy(start + location))
return self.substringWithRange(range)
}
}

Swift 3

extension String
{
func substring(from index: Int) -> String
{
if (index < 0 || index > self.characters.count)
{
print("index \(index) out of bounds")
return ""
}
return self.substring(from: self.characters.index(self.startIndex, offsetBy: index))
}


func substring(to index: Int) -> String
{
if (index < 0 || index > self.characters.count)
{
print("index \(index) out of bounds")
return ""
}
return self.substring(to: self.characters.index(self.startIndex, offsetBy: index))
}


func substring(start: Int, end: Int) -> String
{
if (start < 0 || start > self.characters.count)
{
print("start index \(start) out of bounds")
return ""
}
else if end < 0 || end > self.characters.count
{
print("end index \(end) out of bounds")
return ""
}
let startIndex = self.characters.index(self.startIndex, offsetBy: start)
let endIndex = self.characters.index(self.startIndex, offsetBy: end)
let range = startIndex..<endIndex


return self.substring(with: range)
}


func substring(start: Int, location: Int) -> String
{
if (start < 0 || start > self.characters.count)
{
print("start index \(start) out of bounds")
return ""
}
else if location < 0 || start + location > self.characters.count
{
print("end index \(start + location) out of bounds")
return ""
}
let startIndex = self.characters.index(self.startIndex, offsetBy: start)
let endIndex = self.characters.index(self.startIndex, offsetBy: start + location)
let range = startIndex..<endIndex


return self.substring(with: range)
}
}

用法:

let string = "www.stackoverflow.com"
let substring = string.substringToIndex(string.characters.count-4)

对于 Swift 2.0来说,是这样的:

var string1 = "www.stackoverflow.com"
var index1 = string1.endIndex.advancedBy(-4)
var substring1 = string1.substringToIndex(index1)
func substr(myString: String, start: Int, clen: Int)->String


{
var index2 = string1.startIndex.advancedBy(start)
var substring2 = string1.substringFromIndex(index2)
var index1 = substring2.startIndex.advancedBy(clen)
var substring1 = substring2.substringToIndex(index1)


return substring1
}


substr(string1, start: 3, clen: 5)

是否希望获取字符串的子字符串从开始索引到其中一个字符的最后一个索引?如果是,您可以选择以下 Swift 2.0 + 方法之一。

Methods that require Foundation

Get a substring that includes the last index of a character:

import Foundation


let string = "www.stackoverflow.com"
if let rangeOfIndex = string.rangeOfCharacterFromSet(NSCharacterSet(charactersInString: "."), options: .BackwardsSearch) {
print(string.substringToIndex(rangeOfIndex.endIndex))
}


// prints "www.stackoverflow."

获取一个不包含字符最后索引的子字符串:

import Foundation


let string = "www.stackoverflow.com"
if let rangeOfIndex = string.rangeOfCharacterFromSet(NSCharacterSet(charactersInString: "."), options: .BackwardsSearch) {
print(string.substringToIndex(rangeOfIndex.startIndex))
}


// prints "www.stackoverflow"

如果需要重复这些操作,扩展 String可能是一个很好的解决方案:

import Foundation


extension String {
func substringWithLastInstanceOf(character: Character) -> String? {
if let rangeOfIndex = rangeOfCharacterFromSet(NSCharacterSet(charactersInString: String(character)), options: .BackwardsSearch) {
return self.substringToIndex(rangeOfIndex.endIndex)
}
return nil
}
func substringWithoutLastInstanceOf(character: Character) -> String? {
if let rangeOfIndex = rangeOfCharacterFromSet(NSCharacterSet(charactersInString: String(character)), options: .BackwardsSearch) {
return self.substringToIndex(rangeOfIndex.startIndex)
}
return nil
}
}


print("www.stackoverflow.com".substringWithLastInstanceOf("."))
print("www.stackoverflow.com".substringWithoutLastInstanceOf("."))


/*
prints:
Optional("www.stackoverflow.")
Optional("www.stackoverflow")
*/

不需要 Foundation的方法

获取包含字符最后索引的子字符串:

let string = "www.stackoverflow.com"
if let reverseIndex = string.characters.reverse().indexOf(".") {
print(string[string.startIndex ..< reverseIndex.base])
}


// prints "www.stackoverflow."

获取一个不包含字符最后索引的子字符串:

let string = "www.stackoverflow.com"
if let reverseIndex = string.characters.reverse().indexOf(".") {
print(string[string.startIndex ..< reverseIndex.base.advancedBy(-1)])
}


// prints "www.stackoverflow"

如果需要重复这些操作,扩展 String可能是一个很好的解决方案:

extension String {
func substringWithLastInstanceOf(character: Character) -> String? {
if let reverseIndex = characters.reverse().indexOf(".") {
return self[self.startIndex ..< reverseIndex.base]
}
return nil
}
func substringWithoutLastInstanceOf(character: Character) -> String? {
if let reverseIndex = characters.reverse().indexOf(".") {
return self[self.startIndex ..< reverseIndex.base.advancedBy(-1)]
}
return nil
}
}


print("www.stackoverflow.com".substringWithLastInstanceOf("."))
print("www.stackoverflow.com".substringWithoutLastInstanceOf("."))


/*
prints:
Optional("www.stackoverflow.")
Optional("www.stackoverflow")
*/

我会使用下标(s[start..<end]) :

斯威夫特345

let s = "www.stackoverflow.com"
let start = s.startIndex
let end = s.index(s.endIndex, offsetBy: -4)
let substring = s[start..<end] // www.stackoverflow

我已经用两个子字符串方法扩展了 String,你可以用 from/to range 或 from/length 调用 substring,如下所示:

var bcd = "abcdef".substring(1,to:3)
var cde = "abcdef".substring(2,to:-2)
var cde = "abcdef".substring(2,length:3)


extension String {
public func substring(from:Int = 0, var to:Int = -1) -> String {
if to < 0 {
to = self.length + to
}
return self.substringWithRange(Range<String.Index>(
start:self.startIndex.advancedBy(from),
end:self.startIndex.advancedBy(to+1)))
}
public func substring(from:Int = 0, length:Int) -> String {
return self.substringWithRange(Range<String.Index>(
start:self.startIndex.advancedBy(from),
end:self.startIndex.advancedBy(from+length)))
}
}

Swift 2.0 下面的代码是在 XCode 7.2上测试的。请参考底部附带的截图

import UIKit


class ViewController: UIViewController {


override func viewDidLoad() {
super.viewDidLoad()


var mainText = "http://stackoverflow.com"


var range = Range(start: mainText.startIndex.advancedBy(7), end: mainText.startIndex.advancedBy(24))
var subText = mainText.substringWithRange(range)




//OR Else use below for LAST INDEX


range = Range(start: mainText.startIndex.advancedBy(7), end: mainText.endIndex)
subText = mainText.substringWithRange(range)
}
}

我已经修改了 andrewz 的帖子,使其与 Swift 2.0(或许还有 Swift 3.0)兼容。在我看来,这个扩展更容易理解和 similar to what is available in other languages(像 PHP 一样)。

extension String {


func length() -> Int {
return self.lengthOfBytesUsingEncoding(NSUTF16StringEncoding)
}
func substring(from:Int = 0, to:Int = -1) -> String {
var nto=to
if nto < 0 {
nto = self.length() + nto
}
return self.substringWithRange(Range<String.Index>(
start:self.startIndex.advancedBy(from),
end:self.startIndex.advancedBy(nto+1)))
}
func substring(from:Int = 0, length:Int) -> String {
return self.substringWithRange(Range<String.Index>(
start:self.startIndex.advancedBy(from),
end:self.startIndex.advancedBy(from+length)))
}
}

如果你知道索引,这里有一个简单快捷的方法来获得一个子字符串:

let s = "www.stackoverflow.com"
let result = String(s.characters.prefix(17)) // "www.stackoverflow"

如果你的索引超过了字符串的长度,它不会崩溃应用程序:

let s = "short"
let result = String(s.characters.prefix(17)) // "short"

两个例子都是 斯威夫特3号就绪

斯威夫特3号 XCode 8

func lastIndexOfCharacter(_ c: Character) -> Int? {
return range(of: String(c), options: .backwards)?.lowerBound.encodedOffset
}

Since advancedBy(Int) is gone since Swift 3 use String's method index(String.Index, Int). Check out this String extension with substring and friends:

public extension String {


//right is the first encountered string after left
func between(_ left: String, _ right: String) -> String? {
guard let leftRange = range(of: left), let rightRange = range(of: right, options: .backwards)
, leftRange.upperBound <= rightRange.lowerBound
else { return nil }
    

let sub = self.substring(from: leftRange.upperBound)
let closestToLeftRange = sub.range(of: right)!
return sub.substring(to: closestToLeftRange.lowerBound)
}


var length: Int {
get {
return self.characters.count
}
}


func substring(to : Int) -> String {
let toIndex = self.index(self.startIndex, offsetBy: to)
return self.substring(to: toIndex)
}


func substring(from : Int) -> String {
let fromIndex = self.index(self.startIndex, offsetBy: from)
return self.substring(from: fromIndex)
}


func substring(_ r: Range<Int>) -> String {
let fromIndex = self.index(self.startIndex, offsetBy: r.lowerBound)
let toIndex = self.index(self.startIndex, offsetBy: r.upperBound)
return self.substring(with: Range<String.Index>(uncheckedBounds: (lower: fromIndex, upper: toIndex)))
}


func character(_ at: Int) -> Character {
return self[self.index(self.startIndex, offsetBy: at)]
}


func lastIndexOfCharacter(_ c: Character) -> Int? {
guard let index = range(of: String(c), options: .backwards)?.lowerBound else
{ return nil }
return distance(from: startIndex, to: index)
}
}

更新了 Swift 5的扩展

public extension String {
    

//right is the first encountered string after left
func between(_ left: String, _ right: String) -> String? {
guard
let leftRange = range(of: left), let rightRange = range(of: right, options: .backwards)
, leftRange.upperBound <= rightRange.lowerBound
else { return nil }
        

let sub = self[leftRange.upperBound...]
let closestToLeftRange = sub.range(of: right)!
return String(sub[..<closestToLeftRange.lowerBound])
}
    

var length: Int {
get {
return self.count
}
}
    

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

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

func substring(_ r: Range<Int>) -> String {
let fromIndex = self.index(self.startIndex, offsetBy: r.lowerBound)
let toIndex = self.index(self.startIndex, offsetBy: r.upperBound)
let indexRange = Range<String.Index>(uncheckedBounds: (lower: fromIndex, upper: toIndex))
return String(self[indexRange])
}
    

func character(_ at: Int) -> Character {
return self[self.index(self.startIndex, offsetBy: at)]
}
    

func lastIndexOfCharacter(_ c: Character) -> Int? {
guard let index = range(of: String(c), options: .backwards)?.lowerBound else
{ return nil }
return distance(from: startIndex, to: index)
}
}

用法:

let text = "www.stackoverflow.com"
let at = text.character(3) // .
let range = text.substring(0..<3) // www
let from = text.substring(from: 4) // stackoverflow.com
let to = text.substring(to: 16) // www.stackoverflow
let between = text.between(".", ".") // stackoverflow
let substringToLastIndexOfChar = text.lastIndexOfCharacter(".") // 17

另外,开发人员被迫使用 String.Index而不是普通的 Int,这真的很奇怪。为什么我们要烦恼内部的 String力学,而不是只有简单的 substring()方法?

迅捷4:

extension String {


/// the length of the string
var length: Int {
return self.characters.count
}


/// Get substring, e.g. "ABCDE".substring(index: 2, length: 3) -> "CDE"
///
/// - parameter index:  the start index
/// - parameter length: the length of the substring
///
/// - returns: the substring
public func substring(index: Int, length: Int) -> String {
if self.length <= index {
return ""
}
let leftIndex = self.index(self.startIndex, offsetBy: index)
if self.length <= index + length {
return self.substring(from: leftIndex)
}
let rightIndex = self.index(self.endIndex, offsetBy: -(self.length - index - length))
return self.substring(with: leftIndex..<rightIndex)
}


/// Get substring, e.g. -> "ABCDE".substring(left: 0, right: 2) -> "ABC"
///
/// - parameter left:  the start index
/// - parameter right: the end index
///
/// - returns: the substring
public func substring(left: Int, right: Int) -> String {
if length <= left {
return ""
}
let leftIndex = self.index(self.startIndex, offsetBy: left)
if length <= right {
return self.substring(from: leftIndex)
}
else {
let rightIndex = self.index(self.endIndex, offsetBy: -self.length + right + 1)
return self.substring(with: leftIndex..<rightIndex)
}
}
}

你可以如下测试:

    print("test: " + String("ABCDE".substring(index: 2, length: 3) == "CDE"))
print("test: " + String("ABCDE".substring(index: 0, length: 3) == "ABC"))
print("test: " + String("ABCDE".substring(index: 2, length: 1000) == "CDE"))
print("test: " + String("ABCDE".substring(left: 0, right: 2) == "ABC"))
print("test: " + String("ABCDE".substring(left: 1, right: 3) == "BCD"))
print("test: " + String("ABCDE".substring(left: 3, right: 1000) == "DE"))

检查 https://gitlab.com/seriyvolk83/SwiftEx库。它包含这些和其他有用的方法。

Swift 3

let string = "www.stackoverflow.com"
let first3Characters = String(string.characters.prefix(3)) // www
let lastCharacters = string.characters.dropFirst(4) // stackoverflow.com (it would be a collection)


//or by index
let indexOfFouthCharacter = olNumber.index(olNumber.startIndex, offsetBy: 4)
let first3Characters = olNumber.substring(to: indexOfFouthCharacter) // www
let lastCharacters = olNumber.substring(from: indexOfFouthCharacter) // .stackoverflow.com

Good 文章 for understanding, why do we need this

这里有一个在 Swift 中获取子字符串的简单方法

import UIKit


var str = "Hello, playground"
var res = NSString(string: str)
print(res.substring(from: 4))
print(res.substring(to: 10))

有一件事情增加了响声,那就是重复的 stringVar:

StringVar [ StringVar.index (StringVar.startIndex,offsetBy: ...)

Swift 4

An extension can reduce some of that:

extension String {


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

使用方法:

let string = "abcde"


let to = string[..<string.index(at: 3)] // abc
let from = string[string.index(at: 3)...] // de

值得注意的是,tofromSubstring型(或 String.SubSequance型)。它们不分配新字符串,处理效率更高。

为了得到一个 String类型,Substring需要被抛回到 String:

let backToString = String(from)

这是最终分配字符串的地方。

我还为 Swift 4构建了一个简单的 String 扩展:

extension String {
func subStr(s: Int, l: Int) -> String { //s=start, l=lenth
let r = Range(NSRange(location: s, length: l))!
let fromIndex = self.index(self.startIndex, offsetBy: r.lowerBound)
let toIndex = self.index(self.startIndex, offsetBy: r.upperBound)
let indexRange = Range<String.Index>(uncheckedBounds: (lower: fromIndex, upper: toIndex))


return String(self[indexRange])
}
}

所以你可以这样说:

"Hallo world".subStr(s: 1, l: 3) //prints --> "all"

试试这个 Int-based变通方法:

extension String {
// start and end is included
func intBasedSubstring(_ start: Int, _ end: Int) -> String {
let endOffset: Int = -(count - end - 1)
let startIdx = self.index(startIndex, offsetBy: start)
let endIdx = self.index(endIndex, offsetBy: endOffset)
return String(self[startIdx..<endIdx])
}
}

注意: 这只是一个练习。它不检查边界。修改以适应您的需要。

In Swift 5

我们需要 String.Index而不是简单的整数值来表示索引。

还要记住,当我们尝试从 Swift String(值类型)获取 subString 时,实际上必须使用 Sequence协议进行迭代,该协议返回 String.SubSequence类型而不是 String类型。

要从 String.SubSequence返回 String,请使用 String(subString)

例如:

    let string = "https://stackoverflow.com"
let firstIndex = String.Index(utf16Offset: 0, in: string)
let lastIndex = String.Index(utf16Offset: 6, in: string)
let subString = String(string[firstIndex...lastIndex])
var url = "www.stackoverflow.com"
let str = path.suffix(3)
print(str) //low
var myString = "abcde"


var subString = myString[2,4] // The result will be "cde"


extension String {
subscript(startIndex: Int, endIndex: Int) -> String {
let start = self.index(self.startIndex, offsetBy: startIndex)
let end = self.index(self.startIndex, offsetBy: endIndex)
let range = start...end
return String(self[range])
}
}