【问题标题】:Index of a substring in a string with Swift使用 Swift 对字符串中的子字符串进行索引
【发布时间】:2015-11-25 04:07:32
【问题描述】:

我习惯在 JavaScript 中这样做:

var domains = "abcde".substring(0, "abcde".indexOf("cd")) // Returns "ab"

Swift没有这个功能,类似的怎么办?

【问题讨论】:

  • @eric-d 这不是你提到的那个的副本。 OP 是关于 indexOf() 而不是 substring()。
  • 在 Swift 2 中有一个返回 Range 的 String.rangeOfString(String) 方法。

标签: swift string substring


【解决方案1】:

在 Swift 中这样做是可能的,但它需要更多的行,这是一个函数 indexOf() 做预期的事情:

func indexOf(source: String, substring: String) -> Int? {
    let maxIndex = source.characters.count - substring.characters.count
    for index in 0...maxIndex {
        let rangeSubstring = source.startIndex.advancedBy(index)..<source.startIndex.advancedBy(index + substring.characters.count)
        if source.substringWithRange(rangeSubstring) == substring {
            return index
        }
    }
    return nil
}

var str = "abcde"
if let indexOfCD = indexOf(str, substring: "cd") {
    let distance = str.startIndex.advancedBy(indexOfCD)
    print(str.substringToIndex(distance)) // Returns "ab"
}

这个函数没有优化,但它可以处理短字符串。

【讨论】:

  • 他们还没有将它添加到 Swift 库中,这非常令人沮丧!
  • 我将 extension String 添加到需要对所有其他人可用的 utils.swift 类中
  • 顺便说一句,上面的字符串长度似乎是O(N^2) .. ?
【解决方案2】:

编辑/更新:

Xcode 11.4 • Swift 5.2 或更高版本

import Foundation

extension StringProtocol {
    func index<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> Index? {
        range(of: string, options: options)?.lowerBound
    }
    func endIndex<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> Index? {
        range(of: string, options: options)?.upperBound
    }
    func indices<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> [Index] {
        ranges(of: string, options: options).map(\.lowerBound)
    }
    func ranges<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> [Range<Index>] {
        var result: [Range<Index>] = []
        var startIndex = self.startIndex
        while startIndex < endIndex,
            let range = self[startIndex...]
                .range(of: string, options: options) {
                result.append(range)
                startIndex = range.lowerBound < range.upperBound ? range.upperBound :
                    index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex
        }
        return result
    }
}

用法:

let str = "abcde"
if let index = str.index(of: "cd") {
    let substring = str[..<index]   // ab
    let string = String(substring)
    print(string)  // "ab\n"
}

let str = "Hello, playground, playground, playground"
str.index(of: "play")      // 7
str.endIndex(of: "play")   // 11
str.indices(of: "play")    // [7, 19, 31]
str.ranges(of: "play")     // [{lowerBound 7, upperBound 11}, {lowerBound 19, upperBound 23}, {lowerBound 31, upperBound 35}]

不区分大小写的示例

let query = "Play"
let ranges = str.ranges(of: query, options: .caseInsensitive)
let matches = ranges.map { str[$0] }   //
print(matches)  // ["play", "play", "play"]

正则表达式示例

let query = "play"
let escapedQuery = NSRegularExpression.escapedPattern(for: query)
let pattern = "\\b\(escapedQuery)\\w+"  // matches any word that starts with "play" prefix

let ranges = str.ranges(of: pattern, options: .regularExpression)
let matches = ranges.map { str[$0] }

print(matches) //  ["playground", "playground", "playground"]

【讨论】:

  • 这不太对,因为"ab".indexOf("a")"ab".indexOf("c") 都返回0
  • 对于那些升级到 Swift 3.0 的用户:extension String { func indexOf(string: String) -> String.Index? { return range(of: string, options: .literal, range: nil, locale: nil)?.lowerBound } }
  • 确保您 import Foundation 否则这将不起作用。因为此时您实际上只是在使用 NSString。
  • range: nillocale: nil可以省略,这些参数有一个默认值nil
  • 这是一项繁重的工作——而不是 Swift 原生的方式。请参阅下面的@Inder Kumar Rathore 的答案 - 简单使用 '.range( of: "text" )' 方法
【解决方案3】:

在 Swift 版本 3 中,String 没有类似 -

的功能
str.index(of: String)

如果子字符串需要索引,其中一种方法是获取范围。我们在返回范围的字符串中有以下函数 -

str.range(of: <String>)
str.rangeOfCharacter(from: <CharacterSet>)
str.range(of: <String>, options: <String.CompareOptions>, range: <Range<String.Index>?>, locale: <Locale?>)

例如查找str中第一次出现play的索引

var str = "play play play"
var range = str.range(of: "play")
range?.lowerBound //Result : 0
range?.upperBound //Result : 4

注意:范围是可选的。如果它无法找到字符串,它将使其为零。例如

var str = "play play play"
var range = str.range(of: "zoo") //Result : nil
range?.lowerBound //Result : nil
range?.upperBound //Result : nil

【讨论】:

    【解决方案4】:

    使用 String[Range&lt;String.Index&gt;] 下标可以得到子字符串。您需要起始索引和最后一个索引来创建范围,您可以按照以下方式进行操作

    let str = "abcde"
    if let range = str.range(of: "cd") {
      let substring = str[..<range.lowerBound] // or str[str.startIndex..<range.lowerBound]
      print(substring)  // Prints ab
    }
    else {
      print("String not present")
    }
    

    如果你没有定义这个操作符..&lt;的起始索引,它会使用起始索引。你也可以用str[str.startIndex..&lt;range.lowerBound]代替str[..&lt;range.lowerBound]

    【讨论】:

      【解决方案5】:

      在 Swift 4 中:

      获取字符串中字符的索引:

      let str = "abcdefghabcd"
      if let index = str.index(of: "b") {
         print(index) // Index(_compoundOffset: 4, _cache: Swift.String.Index._Cache.character(1))
      }
      

      使用 Swift 4 从字符串创建子字符串(前缀和后缀):

      let str : String = "ilike"
      for i in 0...str.count {
          let index = str.index(str.startIndex, offsetBy: i) // String.Index
          let prefix = str[..<index] // String.SubSequence
          let suffix = str[index...] // String.SubSequence
          print("prefix \(prefix), suffix : \(suffix)")
      }
      

      输出

      prefix , suffix : ilike
      prefix i, suffix : like
      prefix il, suffix : ike
      prefix ili, suffix : ke
      prefix ilik, suffix : e
      prefix ilike, suffix : 
      

      如果要在 2 个索引之间生成子字符串,请使用:

      let substring1 = string[startIndex...endIndex] // including endIndex
      let subString2 = string[startIndex..<endIndex] // excluding endIndex
      

      【讨论】:

      • 什么是_compoundOffset,到该点为止字符串中的字节数?
      • 这是非常低效的。它将在每次迭代时从起始索引偏移字符串。您应该简单地保留索引位置并在每次迭代时获取索引(之后:)。另请注意,string[startIndex...endIndex] 会崩溃。顺便说一句 Swift 5 或更高版本你可以使用 PartialRangeFrom subscript let substring1 = str[str.startIndex...]
      【解决方案6】:

      这里有三个密切相关的问题:

      • Cocoa NSString 世界(基础)中所有的子字符串查找方法都结束了

      • Foundation NSRange 与 Swift Range 不匹配;前者使用起点和长度,后者使用端点

      • 一般而言,Swift 字符使用String.Index 进行索引,而不是 Int,但 Foundation 字符使用 Int 进行索引,并且它们之间没有简单的直接转换(因为 Foundation 和 Swift对角色的构成有不同的想法)

      考虑到这一切,让我们考虑一下如何写作:

      func substring(of s: String, from:Int, toSubstring s2 : String) -> Substring? {
          // ?
      }
      

      必须使用字符串基础方法在s 中查找子字符串s2。结果范围返回给我们,不是作为 NSRange(即使这是一个 Foundation 方法),而是作为 String.Index 的范围(包装在 Optional 中,以防我们找不到子字符串)。但是,另一个数字from 是一个 Int。因此,我们不能形成任何涉及它们两者的范围。

      但我们不必这样做!我们所要做的就是使用采用String.Index 的方法切掉原始字符串的end,并使用一种方法切掉原始字符串的start这需要一个 Int。幸运的是,存在这样的方法!像这样:

      func substring(of s: String, from:Int, toSubstring s2 : String) -> Substring? {
          guard let r = s.range(of:s2) else {return nil}
          var s = s.prefix(upTo:r.lowerBound)
          s = s.dropFirst(from)
          return s
      }
      

      或者,如果您希望能够将此方法直接应用于字符串,像这样...

      let output = "abcde".substring(from:0, toSubstring:"cd")
      

      ...然后将其作为 String 的扩展:

      extension String {
          func substring(from:Int, toSubstring s2 : String) -> Substring? {
              guard let r = self.range(of:s2) else {return nil}
              var s = self.prefix(upTo:r.lowerBound)
              s = s.dropFirst(from)
              return s
          }
      }
      

      【讨论】:

      • 这是在复制原始字符串吗?如果原始字符串很长并且这是重复操作怎么办?这可以通过 jvm 世界中的零数据复制来完成。
      • @javadba 在派生子字符串时不要复制,这就是子字符串的全部意义所在。基本上,该代码只是遍历一堆指针。
      • 好的 - 我看到了 dropFirst 并没有看到它是如何实现的。我们如何将最终返回的 Substring 提取为 String ?我看到了超长的帖子就在上面 ..
      • 只是强制转换为字符串。我不确定当时是否有副本;可能没有,只要这个和原始字符串都没有被修改,但我不清楚String如何采用写时复制的细节。
      • 好的,谢谢 - 我们开始强制。执行as! String 时,我收到警告“从子字符串转换为字符串总是失败”
      【解决方案7】:

      您是否考虑过使用 NSRange?

      if let range = mainString.range(of: mySubString) {
        //...
      }
      

      【讨论】:

        【解决方案8】:

        Leo Dabus 的回答很棒。这是我根据他的回答使用compactMap 来避免Index out of range 错误的回答。

        斯威夫特 5.1

        extension StringProtocol {
            func ranges(of targetString: Self, options: String.CompareOptions = [], locale: Locale? = nil) -> [Range<String.Index>] {
        
                let result: [Range<String.Index>] = self.indices.compactMap { startIndex in
                    let targetStringEndIndex = index(startIndex, offsetBy: targetString.count, limitedBy: endIndex) ?? endIndex
                    return range(of: targetString, options: options, range: startIndex..<targetStringEndIndex, locale: locale)
                }
                return result
            }
        }
        
        // Usage
        let str = "Hello, playground, playground, playground"
        let ranges = str.ranges(of: "play")
        ranges.forEach {
            print("[\($0.lowerBound.utf16Offset(in: str)), \($0.upperBound.utf16Offset(in: str))]")
        }
        
        // result - [7, 11], [19, 23], [31, 35]
        

        【讨论】:

          【解决方案9】:

          斯威夫特 5

          查找子字符串的索引

          let str = "abcdecd"
          if let range: Range<String.Index> = str.range(of: "cd") {
              let index: Int = str.distance(from: str.startIndex, to: range.lowerBound)
              print("index: ", index) //index: 2
          }
          else {
              print("substring not found")
          }
          

          查找字符索引

          let str = "abcdecd"
          if let firstIndex = str.firstIndex(of: "c") {
              let index = str.distance(from: str.startIndex, to: firstIndex)
              print("index: ", index)   //index: 2
          }
          else {
              print("symbol not found")
          }
          

          【讨论】:

            【解决方案10】:

            斯威夫特 5

                extension String {
                enum SearchDirection {
                    case first, last
                }
                func characterIndex(of character: Character, direction: String.SearchDirection) -> Int? {
                    let fn = direction == .first ? firstIndex : lastIndex
                    if let stringIndex: String.Index = fn(character) {
                        let index: Int = distance(from: startIndex, to: stringIndex)
                        return index
                    }  else {
                        return nil
                    }
                }
            }
            

            测试:

             func testFirstIndex() {
                    let res = ".".characterIndex(of: ".", direction: .first)
                    XCTAssert(res == 0)
                }
                func testFirstIndex1() {
                    let res = "12345678900.".characterIndex(of: "0", direction: .first)
                    XCTAssert(res == 9)
                }
                func testFirstIndex2() {
                    let res = ".".characterIndex(of: ".", direction: .last)
                    XCTAssert(res == 0)
                }
                func testFirstIndex3() {
                    let res = "12345678900.".characterIndex(of: "0", direction: .last)
                    XCTAssert(res == 10)
                }
            

            【讨论】:

            • 在字符串扩展中添加 String. 前缀是多余的。 SearchDirection 就足够了。另请注意,Swift 是一种类型推断语言。如果结果类型不是泛型,则无需显式设置。
            【解决方案11】:

            斯威夫特 5

               let alphabat = "abcdefghijklmnopqrstuvwxyz"
            
                var index: Int = 0
                
                if let range: Range<String.Index> = alphabat.range(of: "c") {
                     index = alphabat.distance(from: alphabat.startIndex, to: range.lowerBound)
                    print("index: ", index) //index: 2
                }
            

            【讨论】:

            猜你喜欢
            • 2022-07-10
            • 2023-04-04
            • 2015-12-12
            • 1970-01-01
            • 2019-08-18
            • 2010-12-15
            • 1970-01-01
            • 2019-07-22
            相关资源
            最近更新 更多