【问题标题】:Extract links from string optimization从字符串优化中提取链接
【发布时间】:2015-04-07 16:47:19
【问题描述】:

我从网站获取数据(HTML 字符串)。我想提取所有链接。我写函数(它有效),但它太慢了......

你能帮我优化一下吗?我可以使用哪些标准功能? 功能逻辑:在文本中找到“http://”sting,然后读取字符串(买char),直到不会得到“\””。

extension String {

subscript (i: Int) -> Character {
    return self[advance(self.startIndex, i)]
}

subscript (i: Int) -> String {
    return String(self[i] as Character)
}

subscript (r: Range<Int>) -> String {
    return substringWithRange(Range(start: advance(startIndex, r.startIndex), end: advance(startIndex, r.endIndex)))
}}



func extractAllLinks(text:String) -> Array<String>{
var stringArray = Array<String>()
var find = "http://" as String

for (var i = countElements(find); i<countElements(text); i++)
{
    var ch:Character = text[i - Int(countElements(find))]
    if (ch == find[0])
    {
        var j = 0
        while (ch == find[j])
        {
            var ch2:Character = find[j]
            if(countElements(find)-1 == j)
            {
                break
            }
            j++
            i++
            ch = text[i - Int(countElements(find))]
        }

        i -= j
        if (j == (countElements(find)-1))
        {
            var str = ""
            for (; text[i - Int(countElements(find))] != "\""; i++)
            {
                str += text[i - Int(countElements(find))]
            }
            stringArray.append(str)
        }

    }
}
return stringArray}

【问题讨论】:

标签: ios string swift


【解决方案1】:

就像上面说的AdamPro13使用NSDataDetector你可以很容易地得到所有的URL,见下面的代码:

let text = "http://www.google.com. http://www.bla.com"
let types: NSTextCheckingType = .Link
var error : NSError?

let detector = NSDataDetector(types: types.rawValue, error: &error)        
var matches = detector!.matchesInString(text, options: nil, range: NSMakeRange(0, count(text)))

for match in matches {
   println(match.URL!)
}

它输出:

http://www.google.com
http://www.bla.com

更新到 Swift 2.0

let text = "http://www.google.com. http://www.bla.com"
let types: NSTextCheckingType = .Link

let detector = try? NSDataDetector(types: types.rawValue)

guard let detect = detector else {
   return
}

let matches = detect.matchesInString(text, options: .ReportCompletion, range: NSMakeRange(0, text.characters.count))

for match in matches {
    print(match.URL!)
}

请记住在上述情况下使用guard 语句,它必须在函数或循环内。

希望对你有所帮助。

【讨论】:

  • 是的,别担心它会帮助别人。
  • 抱歉,它无法处理不包含任何链接的文本。我试过“hello swift”守卫让检测=检测器否则{返回}不起作用
  • 我收到此错误无法使用类型为“(类型:UInt64,错误:inout NSError?)”的参数列表调用类型“NSDataDetector”的初始化程序
【解决方案2】:

这就是 Swift 5.0

的答案
let text = "http://www.google.com. http://www.bla.com"

func checkForUrls(text: String) -> [URL] {
    let types: NSTextCheckingResult.CheckingType = .link

    do {
        let detector = try NSDataDetector(types: types.rawValue)

        let matches = detector.matches(in: text, options: .reportCompletion, range: NSMakeRange(0, text.count))
    
        return matches.compactMap({$0.url})
    } catch let error {
        debugPrint(error.localizedDescription)
    }

    return []
}

checkForUrls(text: text)

【讨论】:

    【解决方案3】:

    非常有用的线程!这是一个在 Swift 1.2 中工作的示例,基于 Victor Sigler 的回答。

        // extract first link (if available) and open it!
        let text = "How technology is changing our relationships to each other: http://t.ted.com/mzRtRfX"
        let types: NSTextCheckingType = .Link
    
        do {
            let detector = try NSDataDetector(types: types.rawValue)
            let matches = detector.matchesInString(text, options: .ReportCompletion, range: NSMakeRange(0, text.characters.count))
            if matches.count > 0 {
                let url = matches[0].URL!
                print("Opening URL: \(url)")
                UIApplication.sharedApplication().openURL(url)
            }
    
        } catch {
            // none found or some other issue
            print ("error in findAndOpenURL detector")
        }
    

    【讨论】:

    • 使用错误处理和guard 语句更新了对 Swift 2.0 的回答。希望对您有所帮助。
    【解决方案4】:

    详情

    • Swift 5.2、Xcode 11.4 (11E146)

    解决方案

    // MARK: DataDetector
    
    class DataDetector {
    
        private class func _find(all type: NSTextCheckingResult.CheckingType,
                                 in string: String, iterationClosure: (String) -> Bool) {
            guard let detector = try? NSDataDetector(types: type.rawValue) else { return }
            let range = NSRange(string.startIndex ..< string.endIndex, in: string)
            let matches = detector.matches(in: string, options: [], range: range)
            loop: for match in matches {
                for i in 0 ..< match.numberOfRanges {
                    let nsrange = match.range(at: i)
                    let startIndex = string.index(string.startIndex, offsetBy: nsrange.lowerBound)
                    let endIndex = string.index(string.startIndex, offsetBy: nsrange.upperBound)
                    let range = startIndex..<endIndex
                    guard iterationClosure(String(string[range])) else { break loop }
                }
            }
        }
    
        class func find(all type: NSTextCheckingResult.CheckingType, in string: String) -> [String] {
            var results = [String]()
            _find(all: type, in: string) {
                results.append($0)
                return true
            }
            return results
        }
    
        class func first(type: NSTextCheckingResult.CheckingType, in string: String) -> String? {
            var result: String?
            _find(all: type, in: string) {
                result = $0
                return false
            }
            return result
        }
    }
    
    // MARK: String extension
    
    extension String {
        var detectedLinks: [String] { DataDetector.find(all: .link, in: self) }
        var detectedFirstLink: String? { DataDetector.first(type: .link, in: self) }
        var detectedURLs: [URL] { detectedLinks.compactMap { URL(string: $0) } }
        var detectedFirstURL: URL? {
            guard let urlString = detectedFirstLink else { return nil }
            return URL(string: urlString)
        }
    }
    

    用法

    let text = """
    Lorm Ipsum is simply dummy text of the printing and typesetting industry. apple.com/ Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. http://gooogle.com. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. yahoo.com It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
    """
    
    print(text.detectedLinks)
    print(text.detectedFirstLink)
    print(text.detectedURLs)
    print(text.detectedFirstURL)
    

    控制台输出

    ["apple.com/", "http://gooogle.com", "yahoo.com"]
    Optional("apple.com/")
    [apple.com/, http://gooogle.com, yahoo.com]
    Optional(apple.com/)
    

    【讨论】:

    • 嗨,伙计,我们怎样才能避免电子邮件检测?我在运行您的代码时遇到了电子邮件问题
    • 它工作得几乎完美!.. 我发现使用这个带有表情符号的 sn-p 会崩溃。我已经用以下 for-in 子句修复了它:for match in matches {guard iterationClosure((string as NSString).substring(with: match.range)) else { break }}
    【解决方案5】:

    实际上有一个名为NSDataDetector 的类会为您检测链接。

    您可以在 NSHipster 上找到它的示例:http://nshipster.com/nsdatadetector/

    【讨论】:

      【解决方案6】:

      我想知道您是否意识到,每次调用 countElements 时,都会调用一个主要的复杂函数,该函数必须扫描字符串中的所有 Unicode 字符,并从中提取扩展的字素簇并对它们进行计数。如果您不知道什么是扩展的字形簇,那么您应该能够想象这并不便宜且主要是矫枉过正。

      只需将其转换为 NSString*,调用 rangeOfString 即可。

      显然你所做的是完全不安全的,因为 http:// 并不意味着有一个链接。您不能只在 html 中查找字符串并希望它有效;它没有。然后是https、Http、hTtp、htTp、httP等等等等等等。但这一切都很容易,因为真正的恐怖请点击 Uttam Sinha 评论中的链接。

      【讨论】:

      • 谢谢!我使用常量var textLength = countElements(text) var findLength = countElements(find)(所以我只使用了 2 次 countElements)。结果 - 快 2 倍。但是 NSDataDetector - 空间速度......我想我会使用它。
      【解决方案7】:

      正如其他人所指出的,您最好使用正则表达式、数据检测器或解析库。但是,作为对您的字符串处理的具体反馈:

      Swift 字符串的关键是接受它们的只进性质。通常,不需要整数索引和随机访问。正如@gnasher729 指出的那样,每次调用count 时,您都在遍历字符串。同样,整数索引扩展是线性的,因此如果在循环中使用它们,很容易意外创建二次或三次复杂度算法。

      但在这种情况下,无需将字符串索引转换为随机访问整数。这是我认为执行类似逻辑的版本(查找前缀,然后从那里查找 " 字符 - 忽略这不适合 https、大写/小写等),仅使用本机字符串索引:

      func extractAllLinks(text: String) -> [String] {
          var links: [String] = []
          let prefix = "http://"
          let prefixLen = count(prefix)
      
          for var idx = text.startIndex; idx != text.endIndex; ++idx {
              let candidate = text[idx..<text.endIndex]
              if candidate.hasPrefix(prefix),
                 let closingQuote = find(candidate, "\"") {
                  let link = candidate[candidate.startIndex..<closingQuote]
                  links.append(link)
                  idx = advance(idx, count(link))
              }
          }
          return links
      }
      
      let text = "This contains the link \"http://www.whatever.com/\" and"
               + " the link \"http://google.com\""
      
      extractAllLinks(text)
      

      如果有其他帮助器,例如findFromIndex 等,或者愿意不使用字符串切片并手动搜索结束字符,那么即使这可以进一步优化(advance(idx, count()) 效率有点低)。

      【讨论】:

      • 谢谢!我明白这个想法,但我不明白代码let candidate = text[idx..&lt;text.endIndex] if candidate.hasPrefix(prefix), let closingQuote = find(candidate, "\"") { let link = candidate[candidate.startIndex..&lt;closingQuote] links.append(link) idx = advance(idx, count(link)) }你能描述一下吗? (并修复,我无法编译它)
      • let candidate = text[idx..&lt;text.endIndex] 创建一个从 idx 开始的子字符串。请注意,子字符串实际上只是对真实字符串的视图,它们不会创建新字符串。然后if candidate.hasPrefix(prefix), let closingQuote = find(candidate, "\"") 说,如果该子字符串以“http”开头,请尝试在其中查找结束引号的索引。如果找到,则它创建一个从开始到结束引号的新子字符串,并将其附加到数组中。最后,它会将idx 向前移动刚刚找到的 url 的长度。
      猜你喜欢
      • 2017-01-21
      • 1970-01-01
      • 2011-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多