【问题标题】:Swift find all occurrences of a substringSwift 查找所有出现的子字符串
【发布时间】:2016-11-03 23:59:13
【问题描述】:

我在这里有一个 Swift 中 String 类的扩展,它返回给定子字符串的第一个字母的索引。

任何人都可以帮我制作它,以便它返回所有出现的数组,而不仅仅是第一个吗?

谢谢。

extension String {
    func indexOf(string : String) -> Int {
        var index = -1
        if let range = self.range(of : string) {
            if !range.isEmpty {
                index = distance(from : self.startIndex, to : range.lowerBound)
            }
        }
        return index
    }
}

例如,我想要[50, 74, 91, 103] 之类的东西,而不是50 的返回值

【问题讨论】:

  • -1 这样的标记值在 Swift 中没有位置。这就是可选项的用途。

标签: swift string swift3


【解决方案1】:

您只需继续推进搜索范围,直到找不到更多子字符串的实例:

extension String {
    func indicesOf(string: String) -> [Int] {
        var indices = [Int]()
        var searchStartIndex = self.startIndex

        while searchStartIndex < self.endIndex,
            let range = self.range(of: string, range: searchStartIndex..<self.endIndex),
            !range.isEmpty
        {
            let index = distance(from: self.startIndex, to: range.lowerBound)
            indices.append(index)
            searchStartIndex = range.upperBound
        }

        return indices
    }
}

let keyword = "a"
let html = "aaaa"
let indicies = html.indicesOf(string: keyword)
print(indicies) // [0, 1, 2, 3]

【讨论】:

  • while let。辉煌。
  • 嗯。这似乎失败了。 let keyword = "hello" let html = "&lt;html&gt;&lt;head&gt;&lt;title&gt;My Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;hello&lt;/body&gt;hello&lt;/html&gt;hello" let indicies = html.indicesOf(string: keyword) print(indicies)fatal error: cannot increment beyond endIndex
  • 一个更简单的例子:let keyword = "a" let html = "a" let indicies = html.indicesOf(string: keyword) print(indicies)fatal error: cannot increment beyond endIndex
  • 如果你把两个相同的关键字放在一行中,它只会找到一个。但是,如果您将三个连续放置,它会找到两个。不过看起来好多了。
  • @Eugenio 如果您允许子字符串重叠,则每次将搜索开始索引推进一个字符:searchStartIndex = string.index(after: searchStartIndex)
【解决方案2】:

我知道我们不是在这里玩代码高尔夫,但对于任何对不使用 vars 或循环的函数式单行实现感兴趣的人,这是另一种可能的解决方案:

extension String {
    func indices(of string: String) -> [Int] {
        return indices.reduce([]) { $1.encodedOffset > ($0.last ?? -1) && self[$1...].hasPrefix(string) ? $0 + [$1.encodedOffset] : $0 }
    }
}

【讨论】:

  • .encodedOffset 已弃用,请改用.utf16Offset(in: self)
【解决方案3】:

这里有 2 个函数。一个返回[Range&lt;String.Index&gt;],另一个返回[Range&lt;Int&gt;]。如果您不需要前者,您可以将其设为私有。我将它设计为模仿range(of:options:range:locale:) 方法,因此它支持所有相同的功能。

import Foundation

extension String {
    public func allRanges(
        of aString: String,
        options: String.CompareOptions = [],
        range: Range<String.Index>? = nil,
        locale: Locale? = nil
    ) -> [Range<String.Index>] {

        // the slice within which to search
        let slice = (range == nil) ? self[...] : self[range!]

        var previousEnd = s.startIndex
        var ranges = [Range<String.Index>]()

        while let r = slice.range(
            of: aString, options: options,
            range: previousEnd ..< s.endIndex,
            locale: locale
        ) {
            if previousEnd != self.endIndex { // don't increment past the end
                    previousEnd = self.index(after: r.lowerBound)
            }
            ranges.append(r)
        }

        return ranges
    }

    public func allRanges(
        of aString: String,
        options: String.CompareOptions = [],
        range: Range<String.Index>? = nil,
        locale: Locale? = nil
    ) -> [Range<Int>] {
        return allRanges(of: aString, options: options, range: range, locale: locale)
            .map(indexRangeToIntRange)
    }


    private func indexRangeToIntRange(_ range: Range<String.Index>) -> Range<Int> {
        return indexToInt(range.lowerBound) ..< indexToInt(range.upperBound)
    }

    private func indexToInt(_ index: String.Index) -> Int {
        return self.distance(from: self.startIndex, to: index)
    }
}

let s = "abc abc  abc   abc    abc"
print(s.allRanges(of: "abc") as [Range<String.Index>])
print()
print(s.allRanges(of: "abc") as [Range<Int>])

【讨论】:

  • 适用于范围。我一直在为我的目的寻找整数,但这对大多数人来说绝对有帮助。
  • @Alexander 你有s.startIndexs.endIndex,我相信他们应该是slice.startIndexslice.endIndex
  • +1 关于这个答案。 Int 索引更容易理解,但它们通常会导致代码性能降低。例如:String.Index 的范围允许您获取像 let r = s[range] 这样的子字符串,这比使用 Int 索引字符串更方便且更快。
  • 这一行给出一个错误: let slice = (range == nil) ? self : self[range!] .. 错误是:Subscript 'subscript(_:)' requires the types 'String.Index' and 'Int' be equivalent
  • @Starwave 这真的是老代码了,我没维护过。
【解决方案4】:

实际上并没有内置函数来执行此操作,但我们可以实现修改后的Knuth-Morris-Pratt algorithm 来获取我们想要匹配的字符串的所有索引。它也应该非常高效,因为我们不需要在字符串上重复调用 range

extension String {
    func indicesOf(string: String) -> [Int] {
        // Converting to an array of utf8 characters makes indicing and comparing a lot easier
        let search = self.utf8.map { $0 }
        let word = string.utf8.map { $0 }

        var indices = [Int]()

        // m - the beginning of the current match in the search string
        // i - the position of the current character in the string we're trying to match
        var m = 0, i = 0
        while m + i < search.count {
            if word[i] == search[m+i] {
                if i == word.count - 1 {
                    indices.append(m)
                    m += i + 1
                    i = 0
                } else {
                    i += 1
                }
            } else {
                m += 1
                i = 0
            }
        }

        return indices
    }
}

【讨论】:

  • 非常好,我很感激你自己实现了这个。 we don't need to repeatedly call range on the string 可能很好,很可能range 也只是进行 KMP 搜索。
  • 这太棒了!我注意到一个小缺点:如果你传入一个长度为 0 的字符串,你会遇到崩溃 - 需要我建议修复吗?
  • 一些建议。只是想指出 Swift 对数组集合的优化很糟糕,而且出于性能原因,这种方法比使用 C/ObjC 要好得多。到目前为止,我还没有看到任何 Swift 本身就具有高性能的东西。
  • 这需要重写,因为不再支持 C 编写
  • 如何进行大小写搜索?
【解决方案5】:

请检查以下答案以在多个位置查找多个项目

func indicesOf(string: String) -> [Int] {
    var indices = [Int]()
    var searchStartIndex = self.startIndex
    
    while searchStartIndex < self.endIndex,
        let range = self.range(of: string, range: searchStartIndex..<self.endIndex),
        !range.isEmpty
    {
        let index = distance(from: self.startIndex, to: range.lowerBound)
        indices.append(index)
        searchStartIndex = range.upperBound
    }
    
    return indices
}

func attributedStringWithColor(_ strings: [String], color: UIColor, characterSpacing: UInt? = nil) -> NSAttributedString {
    let attributedString = NSMutableAttributedString(string: self)
    for string in strings {
        let indexes = self.indicesOf(string: string)
        for index in indexes {
            let range = NSRange(location: index, length: string.count)
            attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: color, range: range)
        }
    }
    
    guard let characterSpacing = characterSpacing else {return attributedString}
    
    attributedString.addAttribute(NSAttributedString.Key.kern, value: characterSpacing, range: NSRange(location: 0, length: attributedString.length))
    
    return attributedString
}

可以如下使用:

let message = "Item 1 + Item 2 + Item 3"
message.attributedStringWithColor(["Item", "+"], color: UIColor.red)

并得到结果

【讨论】:

    【解决方案6】:

    这可以通过递归方法来完成。我用一个数字字符串来测试它。它返回一个可选的Int 数组,这意味着如果找不到子字符串,它将为零。

    extension String {
        func indexes(of string: String, offset: Int = 0) -> [Int]? {
            if let range = self.range(of : string) {
                if !range.isEmpty {
                    let index = distance(from : self.startIndex, to : range.lowerBound) + offset
                    var result = [index]
                    let substr = self.substring(from: range.upperBound)
                    if let substrIndexes = substr.indexes(of: string, offset: index + distance(from: range.lowerBound, to: range.upperBound)) {
                        result.append(contentsOf: substrIndexes)
                    }
                    return result
                }
            }
            return nil
        }
    }
    
    let numericString = "01234567890123456789012345678901234567890123456789012345678901234567890123456789"
    numericString.indexes(of: "3456")
    

    【讨论】:

    • 这不需要递归。这只是不必要的内存打击。
    【解决方案7】:

    我已经调整了接受的答案,以便可以配置 case sensitivity

    extension String {
        func allIndexes(of subString: String, caseSensitive: Bool = true) -> [Int] {
            let subString = caseSensitive ? subString : subString.lowercased()
            let mainString = caseSensitive ? self : self.lowercased()
            var indices = [Int]()
            var searchStartIndex = mainString.startIndex
            while searchStartIndex < mainString.endIndex,
                let range = mainString.range(of: subString, range: searchStartIndex..<mainString.endIndex),
                !range.isEmpty
            {
                let index = distance(from: mainString.startIndex, to: range.lowerBound)
                indices.append(index)
                searchStartIndex = range.upperBound
            }
    
            return indices
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-03-22
      • 1970-01-01
      • 1970-01-01
      • 2015-12-23
      • 1970-01-01
      • 2011-05-01
      • 2012-10-12
      相关资源
      最近更新 更多