正規表現パターンに一致する文字列から部分文字列を抽出したいです。
だから私はこのようなものを探しています:
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
からNSTextCheckingResult
の配列が返されることです。ここでNSTextCheckingResult.range
はNSRange
型です。
NSRange
はRange<String.Index>
と互換性がないので、text.substringWithRange(...)
を使うのを防ぎます。
あまりにも多くのコード行を使用せずにSwiftでこの単純なことを実現する方法をお考えですか?
matchesInString()
メソッドが最初の引数としてString
を取る場合でも、内部的にはNSString
で機能します。また、rangeパラメータはSwift文字列の長さではなくNSString
の長さを使用して指定する必要があります。そうでなければ、 "flags"のような "extended grapheme cluster"では失敗します。
Swift 4(Xcode 9)以降、Swift標準ライブラリは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"]
注:NSRange
は、指定された文字列text
の部分文字列を参照するので、強制的な展開のRange($0.range, in: text)!
は安全です。ただし、避けたい場合は
return results.flatMap {
Range($0.range, in: text).map { String(text[$0]) }
}
代わりに。
(Swift 3以前の古い答え:)
そのため、与えられたSwift文字列をNSString
に変換してから範囲を抽出する必要があります。結果は自動的にSwiftの文字列配列に変換されます。
(Swift 1.2のコードは編集履歴にあります。)
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 []
}
}
例:
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 []
}
}
例:
let string = "????????€4€9"
let matched = matches(for: "[0-9]", in: string)
print(matched)
// ["4", "9"]
私の答えは与えられた答えの上に構築されますが、追加のサポートを追加することで正規表現のマッチングをより堅牢にします。
do/catch
を回避し、guard
構文を使用しますmatchingStrings
をString
の拡張子として追加します。Swift 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")
Swift
//: 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")
Swift 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))
: ""
}
}
}
}
文字列から部分文字列を抽出する場合は、位置だけではなく(絵文字を含む実際の文字列も)。それでは、以下のほうが簡単な解決策かもしれません。
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"]
私は、受け入れられた答えの解決策は残念ながら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 []
}
}
主な違いは以下のとおりです。
Linux上のSwiftでは、Swiftネイティブの同等物が存在しないFoundationオブジェクトにNS
プレフィックスを削除する必要があるようです。 ( Swift evolutionの提案#86 を参照。)
Linux上のSwiftでは、options
初期化メソッドとRegularExpression
メソッドの両方にmatches
引数を指定する必要もあります。
何らかの理由で、String
をNSString
に強制変換することはLinux上のSwiftでは機能しませんが、ソースとしてNSString
を使用して新しいString
を初期化することは機能します。
このバージョンはmacOS/Xcode上のSwift 3でも動作しますが、唯一の例外はNSRegularExpression
の代わりにRegularExpression
を使う必要があるということです。
@ p4bloch一連のキャプチャ括弧から結果をキャプチャしたい場合は、NSTextCheckingResult
の代わりにrange
のrangeAtIndex(index)
メソッドを使用する必要があります。これは上からのSwift2用の@MartinRのメソッドで、キャプチャの括弧に合わせています。返される配列では、最初の結果[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 []
}
}
ユースケースの例としては、例えば "Finding Dory 2016"のようにtitle year
の文字列を分割したいとします。
print ( matches(for: "^(.+)\\s(\\d{4})" , in: "Finding Dory 2016"))
// ["Finding Dory 2016", "Finding Dory", "2016"]
上記の解決策のほとんどは、キャプチャグループを無視した結果として完全一致のみを示します。例:^\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
}
}
これが私のやり方です。これがSwiftでどのように機能するのかという新しい視点をもたらすことを願っています。
以下の例では[]
の間の任意の文字列を取得します
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)")
}
これは、一致した文字列の配列を返す非常に単純な解決策です。
スイフト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)
}
}
NSStringなしで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)!])
}
}
}
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
Lars Blumberg 彼の answer グループをキャプチャし、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をエラーとして持つことは、Swiftが実装しようとしている最後の正規表現を表示するので、エスケープで何が悪かったのかを理解するのに役立ちました。