신속한 추출 정규식 일치


175

정규식 패턴과 일치하는 문자열에서 하위 문자열을 추출하고 싶습니다.

그래서 나는 이와 같은 것을 찾고 있습니다 :

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

이것이 내가 가진 것입니다 :

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

문제는 유형이 어디 matchesInString의 배열 을 제공 한다는 것입니다 .NSTextCheckingResultNSTextCheckingResult.rangeNSRange

NSRange와 호환되지 않으므로 Range<String.Index>사용하지 못합니다.text.substringWithRange(...)

너무 많은 코드 줄 없이이 간단한 일을 신속하게 달성하는 방법에 대한 아이디어가 있습니까?

답변:


313

matchesInString()메소드가 String첫 번째 인수로 a 를 사용 하더라도 내부적으로로 작동 NSString하며 range 매개 변수는 NSStringSwift 문자열 길이가 아닌 길이를 사용하여 제공되어야합니다 . 그렇지 않으면 "플래그"와 같은 "확장 grapheme 클러스터"에 대해 실패합니다.

현재 스위프트 4 (엑스 코드 9), 스위프트 표준 라이브러리 사이의 변환을 제공 기능 Range<String.Index>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 []
    }
}

예:

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

참고 : 지정된 줄의 하위 문자열을 참조 Range($0.range, in: text)!하므로 강제 줄 바꿈 이 안전 NSRange합니다 text. 그러나 피하고 싶다면 다음을 사용하십시오.

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

대신에.


(Swift 3 및 이전 버전의 이전 답변 :)

주어진 Swift 문자열을로 변환 NSString한 다음 범위를 추출해야합니다. 결과는 Swift 문자열 배열로 자동 변환됩니다.

Swift 1.2의 코드는 편집 기록에서 찾을 수 있습니다.

스위프트 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 []
    }
}

예:

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

스위프트 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 []
    }
}

예:

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

9
당신은 나를 미치게하지 않았습니다. 농담 아니야. 정말 고맙습니다!
mitchkman

1
@MathijsSegers : Swift 1.2 / Xcode 6.3의 코드를 업데이트했습니다. 알려 줘서 고마워!
Martin R

1
하지만 태그 사이의 문자열을 검색하려면 어떻게해야합니까? regex101.com/r/cU6jX8/2 와 같은 결과 (일치 정보)가 필요합니다 . 어떤 정규식 패턴을 제안 하시겠습니까?
Peter Kreinz

이 업데이트는 Swift 2가 아니라 Swift 1.2 용입니다. 코드는 Swift 2로 컴파일되지 않습니다.
PatrickNLT

1
감사! 정규식에서 () 사이에 실제로있는 것을 추출하려면 어떻게해야합니까? 예를 들어 "[0-9] {3} ([0-9] {6})"에서 마지막 6 개의 숫자 만 가져오고 싶습니다.
p4bloch

64

내 답변은 주어진 답변을 기반으로하지만 추가 지원을 추가하여 정규식 일치를보다 강력하게 만듭니다.

  • 일치하는 항목 만 반환 할뿐만 아니라 각 일치하는 모든 캡처 그룹도 반환합니다 (아래 예 참조).
  • 이 솔루션 빈 배열을 반환하는 대신 선택적 일치를 지원합니다.
  • 을 피 do/catch콘솔에 인쇄하지 않음으로써 및 차종은의 사용 guard구조
  • 추가 matchingStrings까지 확장String

스위프트 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")

스위프트 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")

스위프트 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
캡처 그룹에 대한 좋은 아이디어. 그러나 왜 "가드"Swiftier가 "do / catch"보다 더 좋습니까?
Martin R

nshipster.com/guard-and-defer 와 같은 사람들 은 Swift 2.0이 중첩 된 if 문보다는 조기 반환 스타일을 장려하는 것 같습니다 . 중첩 된 do / catch 문 IMHO에 대해서도 마찬가지입니다.
Lars Blumberg

try / catch는 Swift의 기본 오류 처리입니다. try?가능한 오류 메시지가 아닌 통화 결과에만 관심이있는 경우 사용할 수 있습니다. 따라서 그렇습니다. guard try? ..하지만 오류를 인쇄하려면 do-block이 필요합니다. 두 가지 방법 모두 스위프 티입니다.
Martin R

3
나는 당신의 멋진 발췌문에 단위 테스트
neoneye

1
내가 이것을 볼 때까지 @MartinR 답변을 기반으로 내 자신을 쓰려고했습니다. 감사!
Oritm

13

위치뿐만 아니라 (이모지를 포함한 실제 문자열) 문자열에서 하위 문자열을 추출하려는 경우. 그러면 다음이 더 간단한 해결책 일 수 있습니다.

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

사용법 예 :

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

다음을 반환합니다 :

["👿⚽️"]

"\ w +"를 사용하면 예기치 않은 ""이 생성 될 수 있습니다.

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

이 문자열 배열을 반환합니다

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

1
이것이 내가 원했던 것입니다
Kyle KIM

1
좋은! Swift 3를 약간 조정해야하지만 훌륭합니다.
Jelle

@Jelle 필요한 조정은 무엇입니까? 저는 스위프트 5.1.3을 사용하고 있습니다
Peter Schorn

9

안타깝게도 받아 들여진 대답의 솔루션이 Linux 용 Swift 3에서 컴파일되지 않는다는 것을 알았습니다. 수정 된 버전은 다음과 같습니다.

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

주요 차이점은 다음과 같습니다.

  1. Linux의 Swift NS는 Swift 네이티브가 아닌 Foundation 객체 에서 접두사를 삭제해야 합니다. ( Swift evolution 제안 # 86 참조 )

  2. Linux의 Swift options에는 RegularExpression초기화 및 matches메소드 모두에 대한 인수를 지정해야합니다 .

  3. 어떤 이유로 든 Linux의 Swift에서는 a String로 강제 변환이 NSString작동하지 않지만 소스가 작동하면 NSStringa String로 새 항목 을 초기화합니다 .

이 버전은 macOS / Xcode의 Swift 3에서도 작동합니다. 단, 이름을 NSRegularExpression대신 사용해야합니다 RegularExpression.


5

@ p4bloch 일련의 캡처 괄호에서 결과를 캡처하려면 대신 대신의 rangeAtIndex(index)방법 을 사용해야합니다 . 위의 Swift2에 대한 @MartinR의 방법은 캡처 괄호에 적합합니다. 반환되는 배열에서 첫 번째 결과 는 전체 캡처이며 개별 캡처 그룹은에서 시작됩니다 . 작업에 주석을 달았 으므로 변경 한 내용을 쉽게 볼 수 있으며 중첩 루프로 대체했습니다.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 []
    }
}

사용 사례의 title year예로 "Finding Dory 2016" 과 같은 문자열을 분할하고 싶다고 할 수 있습니다.

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

이 대답은 나의 하루를 만들었다. 그룹을 추가로 캡처하여 정규 표현을 만족시킬 수있는 솔루션을 검색하는 데 2 ​​시간을 보냈습니다.
Ahmad

작동하지만 범위를 찾지 못하면 충돌이 발생합니다. 이 코드를 수정하여 함수가 반환 [String?]되고 for i in 0..<result.numberOfRanges블록에서! = NSNotFound인 경우에만 일치를 추가하는 테스트를 추가 해야합니다. 그렇지 않으면 nil을 추가해야합니다. 참조 : stackoverflow.com/a/31892241/2805570
stef

4

NSString이없는 스위프트 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)!])
        }
    }
}

위의 솔루션을주의하는 것은 : NSMakeRange(0, self.count)때문에, 정확하지 않은 selfA는 String(= UTF8) 아닌 NSString(= UTF16)을. 따라서 (다른 솔루션에서 사용되는) self.count과 반드시 ​​같을 필요는 없습니다 nsString.length. 범위 계산을NSRange(self.startIndex..., in: self)
pd95

3

위의 대부분의 솔루션은 캡처 그룹을 무시한 결과로 전체 일치 만 제공합니다. 예 : ^ \ d + \ s + (\ d +)

캡처 그룹이 예상대로 일치하도록하려면 (Swift4)와 같은 것이 필요합니다.

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

당신이 그것을 필요로 각각의 결과를 얻기 위해, 단지 첫 번째 결과를하고자하는 경우 이것은 대단한 for index in 0..<matches.count {let lastRange... results.append(matchedString)}
제프

for 절은 다음과 같아야합니다.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

이것이 내가 한 방법입니다, 이것이 스위프트에서 어떻게 작동하는지 새로운 관점을 가져 오기를 바랍니다.

아래의이 예에서는 다음 사이의 문자열을 얻습니다. []

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

이것은 일치하는 문자열 배열을 반환하는 매우 간단한 솔루션입니다

스위프트 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에서 모든 경기와 캡처 그룹을 반환하는 가장 빠른 방법

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 차원 문자열 배열을 반환합니다.

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

보고...

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

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

0

Lars Blumberg 에게 감사의 말을 전하고 Swift 4 와의 전체 경기에 대한 답변얻었 습니다 . 또한 정규 표현식이 유효하지 않을 때 error.localizedDescription 응답을 원하는 사람들을 위해 추가했습니다.

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

localizedDescription을 오류로 사용하면 이스케이프와 관련하여 무엇이 잘못되었는지 이해하는 데 도움이되었습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.