Swift özü normal ifade eşleşmeleri


175

Normal dizgi ile eşleşen bir dizeden alt dizeleri ayıklamak istiyorum.

Yani böyle bir şey arıyorum:

func matchesForRegexInText(regex: String!, text: String!) -> [String] {
   ???
}

Yani sahip olduğum şey bu:

func matchesForRegexInText(regex: String!, text: String!) -> [String] {

    var regex = NSRegularExpression(pattern: regex, 
        options: nil, error: nil)

    var results = regex.matchesInString(text, 
        options: nil, range: NSMakeRange(0, countElements(text))) 
            as Array<NSTextCheckingResult>

    /// ???

    return ...
}

Sorun şu ki matchesInString, bana bir dizi teslim ediyor NSTextCheckingResult, nerede NSTextCheckingResult.rangetür NSRange.

NSRangeile uyumsuz Range<String.Index>, bu yüzden kullanmamı engelliyortext.substringWithRange(...)

Çok fazla kod satırı olmadan bu basit şeyi hızlı bir şekilde nasıl başaracağınız hakkında bir fikriniz var mı?

Yanıtlar:


313

matchesInString()Yöntem Stringilk argüman olarak a alsa bile , dahili olarak çalışır NSStringve range parametresi NSStringSwift dize uzunluğu olarak değil length kullanılarak verilmelidir . Aksi takdirde, "bayraklar" gibi "genişletilmiş grafik kümeleri" için başarısız olur.

İtibariyle Swift 4 (Xcode 9), Swift standart kitaplığı arasında dönüştürme işlevleri sağlar Range<String.Index> ve NSRange.

func matches(for regex: String, in text: String) -> [String] {

    do {
        let regex = try NSRegularExpression(pattern: regex)
        let results = regex.matches(in: text,
                                    range: NSRange(text.startIndex..., in: text))
        return results.map {
            String(text[Range($0.range, in: text)!])
        }
    } catch let error {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

Misal:

let string = "🇩🇪€4€9"
let matched = matches(for: "[0-9]", in: string)
print(matched)
// ["4", "9"]

Not: Zorunlu paketin açılması Range($0.range, in: text)!güvenlidir, çünkü NSRangeverilen dizenin bir alt dizesini ifade eder text. Ancak, bundan kaçınmak istiyorsanız,

        return results.flatMap {
            Range($0.range, in: text).map { String(text[$0]) }
        }

yerine.


(Swift 3 ve öncesi için eski cevap :)

Bu nedenle, verilen Swift dizesini bir biçime dönüştürmeli NSStringve sonra aralıkları çıkarmalısınız . Sonuç otomatik olarak bir Swift dize dizisine dönüştürülecektir.

(Swift 1.2 kodu düzenleme geçmişinde bulunabilir.)

Swift 2 (Xcode 7.3.1):

func matchesForRegexInText(regex: String, text: String) -> [String] {

    do {
        let regex = try NSRegularExpression(pattern: regex, options: [])
        let nsString = text as NSString
        let results = regex.matchesInString(text,
                                            options: [], range: NSMakeRange(0, nsString.length))
        return results.map { nsString.substringWithRange($0.range)}
    } catch let error as NSError {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

Misal:

let string = "🇩🇪€4€9"
let matches = matchesForRegexInText("[0-9]", text: string)
print(matches)
// ["4", "9"]

Swift 3 (Xcode 8)

func matches(for regex: String, in text: String) -> [String] {

    do {
        let regex = try NSRegularExpression(pattern: regex)
        let nsString = text as NSString
        let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length))
        return results.map { nsString.substring(with: $0.range)}
    } catch let error {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

Misal:

let string = "🇩🇪€4€9"
let matched = matches(for: "[0-9]", in: string)
print(matched)
// ["4", "9"]

9
Beni delirmekten kurtardın. Şaka yapmıyorum. Çok teşekkür ederim!
— mitchkman

1
@MathijsSegers: Swift 1.2 / Xcode 6.3 kodunu güncelledim. Bilmeme izin verdiğin için teşekkürler!
— Martin R

1
ancak bir etiket arasındaki dizeleri aramak istersem ne olur? Aynı sonuca ihtiyacım var (maç bilgileri): regex101.com/r/cU6jX8/2 . hangi regex modelini önerirsiniz?
— Peter Kreinz

Güncelleme Swift 2 için değil, Swift 1.2 içindir. Kod Swift 2 ile derlenmez
— PatrickNLT

1
Teşekkürler! Ya sadece normal ifadedeki () arasında gerçekten olanı çıkarmak istiyorsanız? Örneğin, "[0-9] {3} ([0-9] {6})" içinde sadece son 6 rakamı almak istiyorum.
— p4bloch

64

Cevabım verilen cevapların üzerine kuruludur, ancak ek destek ekleyerek normal ifade eşleşmesini daha sağlam hale getirir:

  • Yalnızca eşleşmeleri değil , aynı zamanda her bir eşleşme için tüm yakalama gruplarını döndürür (aşağıdaki örneklere bakın)
  • Boş bir dizi döndürmek yerine, bu çözüm isteğe bağlı eşleşmeleri destekler
  • do/catchKonsola yazdırmamaktan kaçınır ve guardyapıyı kullanır
  • EklentimatchingStrings olarak eklerString

Hızlı 4.2

//: Playground - noun: a place where people can play

import Foundation

extension String {
    func matchingStrings(regex: String) -> [[String]] {
        guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] }
        let nsString = self as NSString
        let results  = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))
        return results.map { result in
            (0..<result.numberOfRanges).map {
                result.range(at: $0).location != NSNotFound
                    ? nsString.substring(with: result.range(at: $0))
                    : ""
            }
        }
    }
}

"prefix12 aaa3 prefix45".matchingStrings(regex: "fix([0-9])([0-9])")
// Prints: [["fix12", "1", "2"], ["fix45", "4", "5"]]

"prefix12".matchingStrings(regex: "(?:prefix)?([0-9]+)")
// Prints: [["prefix12", "12"]]

"12".matchingStrings(regex: "(?:prefix)?([0-9]+)")
// Prints: [["12", "12"]], other answers return an empty array here

// Safely accessing the capture of the first match (if any):
let number = "prefix12suffix".matchingStrings(regex: "fix([0-9]+)su").first?[1]
// Prints: Optional("12")

Hızlı 3

//: Playground - noun: a place where people can play

import Foundation

extension String {
    func matchingStrings(regex: String) -> [[String]] {
        guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] }
        let nsString = self as NSString
        let results  = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))
        return results.map { result in
            (0..<result.numberOfRanges).map {
                result.rangeAt($0).location != NSNotFound
                    ? nsString.substring(with: result.rangeAt($0))
                    : ""
            }
        }
    }
}

"prefix12 aaa3 prefix45".matchingStrings(regex: "fix([0-9])([0-9])")
// Prints: [["fix12", "1", "2"], ["fix45", "4", "5"]]

"prefix12".matchingStrings(regex: "(?:prefix)?([0-9]+)")
// Prints: [["prefix12", "12"]]

"12".matchingStrings(regex: "(?:prefix)?([0-9]+)")
// Prints: [["12", "12"]], other answers return an empty array here

// Safely accessing the capture of the first match (if any):
let number = "prefix12suffix".matchingStrings(regex: "fix([0-9]+)su").first?[1]
// Prints: Optional("12")

Hızlı 2

extension String {
    func matchingStrings(regex: String) -> [[String]] {
        guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] }
        let nsString = self as NSString
        let results  = regex.matchesInString(self, options: [], range: NSMakeRange(0, nsString.length))
        return results.map { result in
            (0..<result.numberOfRanges).map {
                result.rangeAtIndex($0).location != NSNotFound
                    ? nsString.substringWithRange(result.rangeAtIndex($0))
                    : ""
            }
        }
    }
}

1
Yakalama grupları hakkında iyi fikir. Ama neden "bekçi" "do / catch" den daha hızlı?
— Martin R

Nshipster.com/guard-and-defer gibi Swift 2.0'ın iç içe if ifadeleri yerine iç içe geçmiş bir [...] tarzı erken dönüşü cesaretlendirdiğini söyleyen insanlarla aynı fikirdeyim . Aynısı iç içe geçmiş do / catch deyimleri IMHO için de geçerlidir.
— Lars Blumberg

try / catch, Swift'teki yerel hata işlemedir. try?olası bir hata mesajıyla değil, yalnızca aramanın sonucuyla ilgileniyorsanız kullanılabilir. Yani evet, guard try? ..iyi, ama hatayı yazdırmak istiyorsanız bir do-block'a ihtiyacınız var. Her iki yol da Swifty.
— Martin R

3
Güzel pasajınıza birim
— testler ekledim

1
Ben bunu görene kadar kendi @MartinR cevabına göre yazmak üzereydi. Teşekkürler!
— Oritm

13

Bir dizeden alt dizeleri ayıklamak istiyorsanız, yalnızca konumdan değil, emoji içeren gerçek Dizeden de alabilirsiniz. Sonra, aşağıdaki basit bir çözüm olabilir.

extension String {
  func regex (pattern: String) -> [String] {
    do {
      let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpressionOptions(rawValue: 0))
      let nsstr = self as NSString
      let all = NSRange(location: 0, length: nsstr.length)
      var matches : [String] = [String]()
      regex.enumerateMatchesInString(self, options: NSMatchingOptions(rawValue: 0), range: all) {
        (result : NSTextCheckingResult?, _, _) in
        if let r = result {
          let result = nsstr.substringWithRange(r.range) as String
          matches.append(result)
        }
      }
      return matches
    } catch {
      return [String]()
    }
  }
} 

Örnek Kullanım:

"someText 👿🏅👿⚽️ pig".regex("👿⚽️")

Aşağıdakileri döndürür:

["👿⚽️"]

"\ W +" kullanıldığında not beklenmedik bir ""

"someText 👿🏅👿⚽️ pig".regex("\\w+")

Bu String dizisini döndürecek

["someText", "️", "pig"]

1
İstediğim bu
— Kyle KIM

1
Güzel! Swift 3 için küçük bir ayarlamaya ihtiyacı var, ama harika.
— Jelle

@Jelle gereken ayarlama nedir? Hızlı 5.1.3 kullanıyorum
— Peter Schorn

9

Kabul edilen cevap çözümünün maalesef Linux için Swift 3'te derlenmediğini buldum. İşte değiştirilmiş bir sürüm, o zaman bunu yapar:

import Foundation

func matches(for regex: String, in text: String) -> [String] {
    do {
        let regex = try RegularExpression(pattern: regex, options: [])
        let nsString = NSString(string: text)
        let results = regex.matches(in: text, options: [], range: NSRange(location: 0, length: nsString.length))
        return results.map { nsString.substring(with: $0.range) }
    } catch let error {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

Temel farklar:

  1. Linux'ta Swift, NSSwift-yerel eşdeğeri olmayan Foundation nesnelerine önek bırakılmasını gerektiriyor gibi görünüyor . (Bkz. Swift evrim önerisi # 86. )

  2. Linux'ta Swift ayrıca optionshem RegularExpressionbaşlatma hem de matchesyöntem için argümanların belirtilmesini gerektirir .

  3. Nedense, bir halka şiddet Stringbir içine NSStringyeni bir Linux üzerinde Swift çalışmıyor ama başlatılıyor NSStringbir ile Stringkaynak olarak çalışır.

Bu sürüm ayrıca ismini kullanması gerektiğini tek istisna MacOS / Xcode üzerinde Swift 3 ile çalışır NSRegularExpressionyerine RegularExpression.


5

@ p4bloch, bir dizi yakalama parantezinin sonuçlarını yakalamak istiyorsanız , yerine rangeAtIndex(index)yöntemini kullanmanız gerekir . İşte @MartinR'in Swift2 için yakalama parantezleri için uyarlanmış yöntemi. Döndürülen dizide, ilk sonuç tüm yakalama olur ve ardından tek tek yakalama grupları başlar . İşlemi yorumladım (böylece ne değiştirdiğimi görmek daha kolay) ve iç içe döngülerle değiştirdim.NSTextCheckingResultrange[0][1]map

func matches(for regex: String!, in text: String!) -> [String] {

    do {
        let regex = try NSRegularExpression(pattern: regex, options: [])
        let nsString = text as NSString
        let results = regex.matchesInString(text, options: [], range: NSMakeRange(0, nsString.length))
        var match = [String]()
        for result in results {
            for i in 0..<result.numberOfRanges {
                match.append(nsString.substringWithRange( result.rangeAtIndex(i) ))
            }
        }
        return match
        //return results.map { nsString.substringWithRange( $0.range )} //rangeAtIndex(0)
    } catch let error as NSError {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

Örnek bir kullanım örneği, title yearörneğin "Dory 2016'yı Bulma" dizesini bölmek istediğinizi düşünebilirsiniz:

print ( matches(for: "^(.+)\\s(\\d{4})" , in: "Finding Dory 2016"))
// ["Finding Dory 2016", "Finding Dory", "2016"]

Bu cevap benim günümü yaptı. Grupların ek yakalanmasıyla regülasyon ifadesini tatmin edebilecek bir çözüm aramak için 2 saat harcadım.
— Ahmad

Bu çalışır, ancak herhangi bir aralık bulunmazsa kilitlenir. Bu kodu işlev döndürecek şekilde değiştirdim [String?]ve for i in 0..<result.numberOfRangesblokta, sadece aralık! = İse eşleşmeyi ekleyen bir test eklemeniz NSNotFoundgerekir, aksi takdirde nil eklemelidir. Bakınız: stackoverflow.com/a/31892241/2805570
— stef

4

NSString olmadan Swift 4.

extension String {
    func matches(regex: String) -> [String] {
        guard let regex = try? NSRegularExpression(pattern: regex, options: [.caseInsensitive]) else { return [] }
        let matches  = regex.matches(in: self, options: [], range: NSMakeRange(0, self.count))
        return matches.map { match in
            return String(self[Range(match.range, in: self)!])
        }
    }
}

Yukarıdaki çözüme dikkat edin: NSMakeRange(0, self.count)doğru değil, çünkü selfbir String(= UTF8) ve bir NSString(= UTF16) değil. Dolayısıyla, (diğer çözümlerde kullanıldığı self.countgibi) ile aynı olmak zorunda değildir nsString.length. Aralık hesaplamasınıNSRange(self.startIndex..., in: self)
— pd95

3

Yukarıdaki çözümlerin çoğu yalnızca yakalama gruplarını yok sayarak tam eşleşmeyi verir, örneğin: ^ \ d + \ s + (\ d +)

Yakalama grubu eşleşmelerini beklendiği gibi elde etmek için (Swift4) gibi bir şeye ihtiyacınız var:

public extension String {
    public func capturedGroups(withRegex pattern: String) -> [String] {
        var results = [String]()

        var regex: NSRegularExpression
        do {
            regex = try NSRegularExpression(pattern: pattern, options: [])
        } catch {
            return results
        }
        let matches = regex.matches(in: self, options: [], range: NSRange(location:0, length: self.count))

        guard let match = matches.first else { return results }

        let lastRangeIndex = match.numberOfRanges - 1
        guard lastRangeIndex >= 1 else { return results }

        for i in 1...lastRangeIndex {
            let capturedGroupIndex = match.range(at: i)
            let matchedString = (self as NSString).substring(with: capturedGroupIndex)
            results.append(matchedString)
        }

        return results
    }
}

Eğer ihtiyacı olan her sonuç almak için, sadece ilk sonucu isteyen konum bu harika for index in 0..<matches.count {etrafındalet lastRange... results.append(matchedString)}
— Geoff

for cümlesi şöyle görünmelidir:for i in 1...lastRangeIndex { let capturedGroupIndex = match.range(at: i) if capturedGroupIndex.location != NSNotFound { let matchedString = (self as NSString).substring(with: capturedGroupIndex) results.append(matchedString.trimmingCharacters(in: .whitespaces)) } }
— CRE8IT

2

İşte böyle yaptım, umarım Swift üzerinde nasıl çalıştığını yeni bir bakış açısı getirir.

Aşağıdaki bu örnekte ben arasında herhangi bir dize alacak []

var sample = "this is an [hello] amazing [world]"

var regex = NSRegularExpression(pattern: "\\[.+?\\]"
, options: NSRegularExpressionOptions.CaseInsensitive 
, error: nil)

var matches = regex?.matchesInString(sample, options: nil
, range: NSMakeRange(0, countElements(sample))) as Array<NSTextCheckingResult>

for match in matches {
   let r = (sample as NSString).substringWithRange(match.range)//cast to NSString is required to match range format.
    println("found= \(r)")
}

2

Bu, eşleşmelerle bir dize dizisi döndüren çok basit bir çözümdür

Hızlı 3.

internal func stringsMatching(regularExpressionPattern: String, options: NSRegularExpression.Options = []) -> [String] {
        guard let regex = try? NSRegularExpression(pattern: regularExpressionPattern, options: options) else {
            return []
        }

        let nsString = self as NSString
        let results = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))

        return results.map {
            nsString.substring(with: $0.range)
        }
    }

2

Swift 5'teki tüm karşılaşmaları döndürmenin ve grupları yakalamanın en hızlı yolu

extension String {
    func match(_ regex: String) -> [[String]] {
        let nsString = self as NSString
        return (try? NSRegularExpression(pattern: regex, options: []))?.matches(in: self, options: [], range: NSMakeRange(0, count)).map { match in
            (0..<match.numberOfRanges).map { match.range(at: $0).location == NSNotFound ? "" : nsString.substring(with: match.range(at: $0)) }
        } ?? []
    }
}

2 boyutlu dizelerden oluşan bir dizi döndürür:

"prefix12suffix fix1su".match("fix([0-9]+)su")

İadeler...

[["fix12su", "12"], ["fix1su", "1"]]

// First element of sub-array is the match
// All subsequent elements are the capture groups

0

Lars Blumberg'e , Swift 4 ile grup yakalama ve tam maçlar için verdiği cevap için çok teşekkürler , bu da bana çok yardımcı oldu. Ben de onların regex geçersiz olduğunda bir error.localizedDescription yanıtı isteyen insanlar için bir ek yaptım:

extension String {
    func matchingStrings(regex: String) -> [[String]] {
        do {
            let regex = try NSRegularExpression(pattern: regex)
            let nsString = self as NSString
            let results  = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))
            return results.map { result in
                (0..<result.numberOfRanges).map {
                    result.range(at: $0).location != NSNotFound
                        ? nsString.substring(with: result.range(at: $0))
                        : ""
                }
            }
        } catch let error {
            print("invalid regex: \(error.localizedDescription)")
            return []
        }
    }
}

Benim için localizedDescription öğesinin hata olarak alınması, kaçan neyin yanlış gittiğini anlamaya yardımcı oldu, çünkü son regex swift'in uygulamaya çalıştığını gösteriyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.