String子串如何在Swift中工作

时间:2016-09-24 14:17:10

标签: swift string range substring

我一直在使用Swift 3更新我的一些旧代码和答案但是当我使用子字符串进入Swift Strings和Indexing时,事情变得令人困惑。

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

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。这是子串的后续问题和答案。我在下面添加一个答案来说明它们是如何使用的。

20 个答案:

答案 0 :(得分:699)

enter image description here

以下所有示例均使用

var str = "Hello, playground"

Swift 4

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

另一方面,子字符串是一个返回原始字符串的引用。以下是来自documentation的图片,说明了这一点。

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

let myString = String(mySubstring)

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

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

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

获取子字符串

您可以使用下标或许多其他方法从字符串中获取子字符串(例如,prefixsuffixsplit)。不过,您仍然需要使用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 个字符。

字符串中的范围

我们再次使用下标。

Substring

String转换为String

不要忘记,当您准备保存子字符串时,应将其转换为let myString = String(mySubstring) ,以便清除旧字符串的内存。

Int

使用Int索引扩展名?

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

我不得不说,我希望Swift团队找到一种方法来在将来抽象出Int。但在他们之前,我选择使用他们的API。它帮助我记住String操作不仅仅是简单的0% [Connecting to gb.archive.ubuntu.com (2001:67c:1560:8001::14)] [Connecting to security.ubuntu.com (2001:67c:1560:8001::11)]索引查找。

答案 1 :(得分:153)

我对Swift的String访问模型感到非常沮丧:一切都必须是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 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

答案 2 :(得分:58)

Swift 4 Extension:

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

用法:

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

或unicode:

let s = ""
s[0..<1] // ""

答案 3 :(得分:19)

Swift 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

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

这就是为什么一旦要清理原始String,就应该将结果复制到新的String中。您可以使用常规构造函数执行此操作:

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

}

答案 4 :(得分:7)

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

然而,我从经验中意识到,我总是最终会遭遇试图打击&#34;改变&#34;比如处理多字节字符,如果你正在关注全球观众,这是不可避免的。

所以我决定认识并尊重Apple工程师所做的努力,并在他们提出这个&#34;恐怖&#34;时理解他们的心态。方法

为什么不弄清楚字符串现在是如何工作的,而不是创建仅仅是一种让你的生活更轻松的解决方法的扩展(

例如,我有这个代码正在使用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,我终于理解了将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))

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

答案 5 :(得分: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
    }
}

这是我创建的博客文章的链接,用于处理swift中的字符串操作。 String manipulation in swift (Covers swift 4 as well)

Or you can see this gist on github

答案 6 :(得分:4)

同样的沮丧,这不应该那么难......

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

  var legend = svg.selectAll(".legend")
      .data(ageNames.slice().reverse())
    .enter().append("g")
      .attr("class", "legend")
      .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });

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

答案 7 :(得分:4)

我是Swift 3中的新手,但是看String(索引)语法进行类比我认为索引就像一个&#34;指针&#34;约束为字符串,Int可以作为一个独立的对象。使用base + offset语法,然后我们可以从字符串中获取第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

对于使用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个,我使用offsetBY:f + 1,因为订阅范围使用..&lt; (半开操作员),不包括第f个位置。

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

答案 8 :(得分:3)

快速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”

答案 9 :(得分:1)

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

{{1}}

答案 10 :(得分:1)

Swift 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

答案 11 :(得分:1)

String 的特殊性主要在其他答案中得到解决。解释一下:String 有一个特定的 Index,它不是 Int 类型,因为字符串元素在一般情况下没有相同的大小。因此,String 不符合 RandomAccessCollection 并且访问特定索引意味着遍历集合,这不是 O(1) 操作。

许多答案都提出了使用范围的变通方法,但它们可能会导致代码效率低下,因为它们使用非 O(1) 的字符串方法 (index(from:), index(:offsetBy:), ...)。< /p>

要访问数组中的字符串元素,您应该使用 Array

let array = Array("Hello, world!")
let letter = array[5]

这是一个权衡,数组创建是一个 O(n) 操作,但数组访问则是 O(1)。您可以在需要时使用 String(array) 转换回字符串。

答案 12 :(得分:1)

在上述基础上,我需要在非打印字符处拆分字符串,以删除非打印字符。我开发了两种方法:

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

答案 13 :(得分:1)

快速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

答案 14 :(得分:0)

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) 中的 offsetBy,因此修剪后的字符串将从索引 2 开始(即从第二个字符开始)和 endIndex = -1 -> str.index(str.endIndex, offsetBy: -1) 中的 offsetBy,即 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) 因此,正在打印完整的字符串

答案 15 :(得分:0)

通过这种简短而简单的方法来实现。

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

答案 16 :(得分:0)

我的想法很机械。这是基础知识...

快捷键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...]

答案 17 :(得分:0)

这里有一个更通用的实现:

该技术仍然使用index来保持Swift的标准,并暗示一个完整的Character。

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

答案 18 :(得分:0)

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

使用扩展名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"

答案 19 :(得分:-1)

Swift 5
let desiredIndex: Int = 7 let substring = str[String.Index(encodedOffset: desiredIndex)...]
此子字符串变量将为您提供结果。
只需在这里将Int转换为Index,然后即可拆分字符串。除非出现错误。