Swift 正則表達式完整教程

NSRegularExpression

正則表達式,又稱正規表示法、常規表示法。(英語:Regular Expression,在代碼中常簡寫爲regex、regexp或RE),計算機科學的一個概念。正則表達式使用單個字符串來描述、匹配一系列符合某個句法規則。在不少文本編輯器裏,正則表達式一般被用來檢索、替換那些符合某個模式的文本。git

枚舉類型

typedef NS_OPTIONS(NSUInteger, NSRegularExpressionOptions) {
     NSRegularExpressionCaseInsensitive          = 1 << 0,   // 不區分大小寫的
     NSRegularExpressionAllowCommentsAndWhitespace  = 1 << 1,   // 忽略空格和# (註釋符)
     NSRegularExpressionIgnoreMetacharacters        = 1 << 2,   // 總體化
     NSRegularExpressionDotMatchesLineSeparators    = 1 << 3,   // 匹配任何字符,包括行分隔符
     NSRegularExpressionAnchorsMatchLines          = 1 << 4,   // 容許^和$在匹配的開始和結束行
     NSRegularExpressionUseUnixLineSeparators      = 1 << 5,   // (查找範圍爲整個的話無效)
     NSRegularExpressionUseUnicodeWordBoundaries    = 1 << 6    // (查找範圍爲整個的話無效)
     };
複製代碼
typedef NS_OPTIONS(NSUInteger, NSMatchingOptions)  {
   NSMatchingReportProgress         = 1 << 0, //找到最長的匹配字符串後調用block回調
   NSMatchingReportCompletion       = 1 << 1, //找到任何一個匹配串後都回調一次block
   NSMatchingAnchored               = 1 << 2, //從匹配範圍的開始處進行匹配
   NSMatchingWithTransparentBounds  = 1 << 3, //容許匹配的範圍超出設置的範圍
   NSMatchingWithoutAnchoringBounds = 1 << 4  //禁止^和$自動匹配行仍是和結束
     };
複製代碼

此枚舉值只在block方法中用到正則表達式

typedef NS_OPTIONS(NSUInteger, NSMatchingFlags) {
   NSMatchingProgress               = 1 << 0, //匹配到最長串是被設置     
   NSMatchingCompleted              = 1 << 1, //所有分配完成後被設置    
   NSMatchingHitEnd                 = 1 << 2, //匹配到設置範圍的末尾時被設置   
   NSMatchingRequiredEnd            = 1 << 3, //當前匹配到的字符串在匹配範圍的末尾時被設置     
   NSMatchingInternalError          = 1 << 4  //因爲錯誤致使的匹配失敗時被設置
     };
複製代碼

方法

1. 返回全部匹配結果的集合(適合,從一段字符串中提取咱們想要匹配的全部數據)
 *  - (NSArray *)matchesInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range;
 2. 返回正確匹配的個數(經過等於0,來驗證郵箱,電話什麼的,代替NSPredicate)
 *  - (NSUInteger)numberOfMatchesInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range;
 3. 返回第一個匹配的結果。注意,匹配的結果保存在  NSTextCheckingResult 類型中
 *  - (NSTextCheckingResult *)firstMatchInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range;
 4. 返回第一個正確匹配結果字符串的NSRange
 *  - (NSRange)rangeOfFirstMatchInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range;
 5. block方法
 *  - (void)enumerateMatchesInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range usingBlock:(void (^)(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop))block;
複製代碼

替換方法算法

- (NSString *)stringByReplacingMatchesInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range withTemplate:(NSString *)templ;
- (NSUInteger)replaceMatchesInString:(NSMutableString *)string options:(NSMatchingOptions)options range:(NSRange)range withTemplate:(NSString *)templ;
- (NSString *)replacementStringForResult:(NSTextCheckingResult *)result inString:(NSString *)string offset:(NSInteger)offset template:(NSString *)templ;
複製代碼

使用案例

字符串的替換swift

let test = "sdgreihen一個安靜的晚上jlosd一個"
let regex = "一個"
let RE = try NSRegularExpression(pattern: regex, options: .caseInsensitive)
let modified = RE.stringByReplacingMatches(in: test, options: .reportProgress, range: NSRange(location: 0, length: test.count), withTemplate: "是的")
複製代碼

打印數組

sdgreihen是的安靜的晚上jlosd是的
複製代碼

字符串的匹配bash

let test = "sdgreihendfjbhiidfjdbjb"
let regex = "jb"
let RE = try NSRegularExpression(pattern: regex, options: .caseInsensitive)
let matchs = RE.matches(in: test, options: .reportProgress, range: NSRange(location: 0, length: test.count))
print(matchs.count)
複製代碼

可是有的時候,咱們須要匹配的不是準確的字符串,是模糊匹配,像檢測手機號,郵箱等等app

let test = "1832321108"
let regex = "^1[0-9]{10}$"
let RE = try NSRegularExpression(pattern: regex, options: .caseInsensitive)
let matchs = RE.matches(in: test, options: .reportProgress, range: NSRange(location: 0, length: test.count))
print(matchs.count)
複製代碼

咱們接下來學習一下正則表達式的規則編輯器

正則表達式

咱們先來寫一個方便測試的工具ide

/// 正則匹配
///
/// - Parameters:
///   - regex: 匹配規則
///   - validateString: 匹配對test象
/// - Returns: 返回結果
func RegularExpression (regex:String,validateString:String) -> [String]{
    do {
        let regex: NSRegularExpression = try NSRegularExpression(pattern: regex, options: [])
        let matches = regex.matches(in: validateString, options: [], range: NSMakeRange(0, validateString.count))
        
        var data:[String] = Array()
        for item in matches {
            let string = (validateString as NSString).substring(with: item.range)
            data.append(string)
        }
        
        return data
    }
    catch {
        return []
    }
}


/// 字符串的替換
///
/// - Parameters:
///   - validateString: 匹配對象
///   - regex: 匹配規則
///   - content: 替換內容
/// - Returns: 結果
func replace(validateString:String,regex:String,content:String) -> String {
    do {
        let RE = try NSRegularExpression(pattern: regex, options: .caseInsensitive)
        let modified = RE.stringByReplacingMatches(in: validateString, options: .reportProgress, range: NSRange(location: 0, length: validateString.count), withTemplate: content)
        return modified
    }
    catch {
        return validateString
    }
   
}

複製代碼

本章節按照下面順序研究函數

  • 正則表達式字符匹配攻略
  • 正則表達式位置匹配攻略
  • 正則表達式括號的做用
  • 正則表達式回溯法原理
  • 正則表達式的拆分

第一章、正則表達式字符匹配攻略

正則表達式是匹配模式,要麼匹配字符,要麼匹配位置

  • 一、兩種模糊匹配
  • 二、字符組
  • 三、量詞
  • 四、分支結構

一、兩種模糊匹配

若是正則只有精確匹配是沒多大意義的,好比hello,也只能匹配字符串中的hello這個子串

正則表達式之因此強大,是由於其能實現模糊匹配。

而模糊匹配,有兩個方向上的「模糊」:橫向模糊和縱向模糊。

1.一、橫向模糊匹配

橫向模糊指的是,一個正則可匹配的字符串的長度不是固定的,能夠是多種狀況的。

其實現的方式是使用量詞。譬如{m,n},表示連續出現最少m次,最多n次。

好比ab{2,5}c表示匹配這樣一個字符串:第一個字符是a,接下來是2到5個字符b,最後是字符c。測試以下:

let regex = "ab{2,5}c"
let validate = "abc abbc abbbc abbbbc abbbbbc abbbbbbc"
let result = RegularExpression(regex: regex, validateString: validate)

//打印結果
["abbc", "abbbc", "abbbbc", "abbbbbc"]
複製代碼

1.二、縱向模糊匹配

縱向模糊指的是,一個正則匹配的字符串,具體到某一位字符時,它能夠不是某個肯定的字符,能夠有多種可能。

其實現的方式是使用字符組。譬如[abc],表示該字符是能夠字符abc中的任何一個。

好比a[123]b能夠匹配以下三種字符串:a1ba2ba3b。測試以下

let regex = "a[123]b"
let validate = "a0b a1b a2b a3b a4b"
let result = RegularExpression(regex: regex, validateString: validate)
print(result)


//打印結果
["a1b", "a2b", "a3b"]
複製代碼

二、字符組

須要強調的是,雖叫字符組(字符類),但只是其中一個字符。例如[abc],表示匹配一個字符,它能夠是abc之一。

  • 一、範圍表示法:若是字符組裏的字符特別多的話,可使用範圍表示法。好比[123456abcdefGHIJKLM],能夠寫成[1-6a-fG-M]。用連字符-來省略和簡寫

  • 二、 排除字符組:縱向模糊匹配,還有一種情形就是,某位字符能夠是任何東西,但就不能是"a"、"b"、"c"。此時就是排除字符組(反義字符組)的概念。例如[^abc],表示是一個除"a"、"b"、"c"以外的任意一個字符。字符組的第一位放^(脫字符),表示求反的概念。

2.一、常見的簡寫形式

有了字符組的概念後,一些常見的符號咱們也就理解了。由於它們都是系統自帶的簡寫形式

正則表達式 匹配區間 記憶方式
\d [0-9]表示是一位數字 其英文是digit(數字)
\D [^0-9]表示除數字外的任意字符
\w [0-9a-zA-Z_]表示數字、大小寫字母和下劃線 w是word的簡寫,也稱單詞字符
\W [^0-9a-zA-Z_] 非單詞字符
\s [ \t\v\n\r\f]表示空白符,包括空格、水平製表符、垂直製表符、換行符、回車符、換頁符 s是space character的首字母
\S [^ \t\v\n\r\f] 非空白符
. [^\n\r\u2028\u2029]通配符,表示幾乎任意字符。換行符、回車符、行分隔符和段分隔符除外

特殊字符

2.二、量詞

量詞也稱重複。掌握{m,n}的準確含義後,只須要記住一些簡寫形式。

  • {m,} 表示至少出現m次
  • {m} 等價於{m,m},表示出現m次
  • ? 等價於{0,1},表示出現或者不出現。記憶方式:問號的意思表示,有嗎?
  • + 等價於{1,},表示出現至少一次。記憶方式:加號是追加的意思,得先有一個,而後才考慮追加。
  • * 等價於{0,},表示出現任意次,有可能不出現。記憶方式:看看天上的星星,可能一顆沒有,可能零散有幾顆,可能數也數不過來。

貪婪匹配:它會盡量多的匹配。你能給我6個,我就要5個。你能給我3個,我就3要個。反正只要在能力範圍內,越多越好。

惰性匹配:就是儘量少的匹配:

let regex = "\\d{2,5}"
let validate = "123 1234 12345 123456"
let result = RegularExpression(regex: regex, validateString: validate)
print(result)
//打印結果
["123", "1234", "12345", "12345"]


---------------------------------
let regex = "\\d{2,5}?"
let validate = "123 1234 12345 123456"
let result = RegularExpression(regex: regex, validateString: validate)
print(result)

//打印結果
["12", "12", "34", "12", "34", "12", "34", "56"]
複製代碼

經過在量詞後面加個問號就能實現惰性匹配,所以全部惰性匹配情形以下:

{m,n}? {m,}? ?? +? *?

2.三、多選分支

一個模式能夠實現橫向和縱向模糊匹配。而多選分支能夠支持多個子模式任選其一。

具體形式以下:(p1|p2|p3),其中p一、p2和p3是子模式,用|(管道符)分隔,表示其中任何之一

例如要匹配goodnice可使用good|nice。測試以下:

let regex = "good|nice"
let validate = "good idea, nice try."
let result = RegularExpression(regex: regex, validateString: validate)
print(result)

//打印結果
["good", "nice"]
複製代碼

但有個事實咱們應該注意,好比我用 good|goodbye,去匹配goodbye字符串時,結果是good

let regex = "good|goodbye"
let validate = "goodbye"
let result = RegularExpression(regex: regex, validateString: validate)
print(result)

//打印結果
["good"]

複製代碼

而把正則改爲goodbye|good,結果是

let regex = "goodbye|good"
let validate = "goodbye"
let result = RegularExpression(regex: regex, validateString: validate)
print(result)

//打印結果
["goodbye"]
複製代碼

也就是說,分支結構也是惰性的,即當前面的匹配上了,後面的就再也不嘗試了。

第二章、正則表達式位置匹配攻略

匹配攻略主要是從如下幾個方面介紹

  • 一、什麼是位置?
  • 二、如何匹配位置?

1. 什麼是位置呢

位置是相鄰字符之間的位置。好比,下圖中箭頭所指的地方

2. 如何匹配位置呢?

2.一、^$

  • ^(脫字符)匹配開頭,在多行匹配中匹配行開頭
  • $(美圓符號)匹配結尾,在多行匹配中匹配行結尾。

好比咱們把字符串的開頭和結尾用"#"替換

let regex = "^|$"
let validate = "hello"
let result = replace(validateString: validate, regex: regex, content: "#")
print(result)

//打印結果
#hello#
複製代碼

2.二、 \b\B

\b是單詞邊界,具體就是\w\W之間的位置,也包括\w^之間的位置,也包括\w$之間的位置。

let regex = "\\b"
let validate = "[JS] Lesson_01.mp4"
let result = replace(validateString: validate, regex: regex, content: "#")
print(result)
//[#JS#] #Lesson_01#.#mp4#
複製代碼

首先,咱們知道,\w是字符組[0-9a-zA-Z_]的簡寫形式,即\w是字母數字或者下劃線的中任何一個字符。而\W是排除字符組[^0-9a-zA-Z_]的簡寫形式,即\W\w之外的任何一個字符。

此時咱們能夠看看"[#JS#] #Lesson_01#.#mp4#"中的每個"#",是怎麼來的。

  • 第一個"#",兩邊是"["與"J",是\W和\w之間的位置。
  • 第二個"#",兩邊是"S"與"]",也就是\w和\W之間的位置。
  • 第三個"#",兩邊是空格與"L",也就是\W和\w之間的位置。
  • 第四個"#",兩邊是"1"與".",也就是\w和\W之間的位置。
  • 第五個"#",兩邊是"."與"m",也就是\W和\w之間的位置。
  • 第六個"#",其對應的位置是結尾,但其前面的字符"4"是\w,即\w和$之間的位置。

\B就是\b的反面的意思,非單詞邊界。例如在字符串中全部位置中,扣掉\b,剩下的都是\B的。

let regex = "\\B"
let validate = "[JS] Lesson_01.mp4"
let result = replace(validateString: validate, regex: regex, content: "#")
print(result)
//#[J#S]# L#e#s#s#o#n#_#0#1.m#p#4
複製代碼

2.三、(?=p)(?!p)

(?=p),其中p是一個子模式,即p前面的位置

好比(?=l),表示l字符前面的位置,例如:

let regex = "(?=l)"
let validate = "hello"
let result = replace(validateString: validate, regex: regex, content: "#")
print(result)

//he#l#lo
複製代碼

(?!p)就是(?=p)的反面意思

let regex = "(?!l)"
let validate = "hello"
let result = replace(validateString: validate, regex: regex, content: "#")
print(result)
複製代碼

三、案例

數字的千位分隔符表示法

好比把"12345678",變成"12 345 678"。

let regex = "(?=(\\d{3})+$)"
let validate = "12345678"
let result = replace(validateString: validate, regex: regex, content: " ")
print(result)
//12 345 678
複製代碼

思路:

  • 一、 先把後三位弄出一個空格,使用(?=\d{3}$)
  • 二、由於每三位出現一次空格,全部可使用量詞+,最終就是(?=(\\d{3})+$)

可是當咱們在對123456789切分時,發現最前面多一個空格,此時咱們須要不設置開頭,可使用(?!^)。爲了看出來效果,咱們使用#來代替空格

let regex = "(?=(\\d{3})+$)"
let validate = "123456789"
let result = replace(validateString: validate, regex: regex, content: "#")
print(result)
//#123#456#789

let regex = "(?!^)(?=(\\d{3})+$)"
let validate = "123456789"
let result = replace(validateString: validate, regex: regex, content: "#")
print(result)
//123#456#789
複製代碼

驗證密碼問題

密碼長度6-12位,由數字、小寫字符和大寫字母組成,但必須至少包括2種字符。

針對這個問題咱們能夠分步實現

  • 一、密碼長度6-12位,由數字、小寫字符和大寫字母組成。正則表達式爲^[0-9A-Za-z]{6,12}$

  • 二、判斷是否包含有某一種字符。要求的必須包含數字,正則表達式爲(?=.*[0-9])(?=.*[0-9])表示該位置後面的字符匹配.*[0-9],有任何多個任意字符,後面再跟個數字。翻譯成大白話,就是接下來的字符,必須包含個數字。

  • 三、同時包含具體兩種字符,好比同時包含數字和小寫字母,正則表達式爲(?=.*[0-9])(?=.*[a-z])

  • 四、完整的正則表達式爲(?=.*[0-9])(?=.*[a-z])^[0-9A-Za-z]{6,12}$

第三章、正則表達式括號的做用

無論哪門語言中都有括號。正則表達式也是一門語言,而括號的存在使這門語言更爲強大。

內容包括:

  • 一、分組和分支結構
  • 二、引用分組
  • 三、反向引用
  • 四、非捕獲分組

一、分組和分支結構

分組

咱們知道a+匹配連續出現的「a」,而要匹配連續出現的「ab」時,須要使用(ab)+

其中括號是提供分組功能,使量詞+做用於ab這個總體,測試以下

let regex = "(ab)+"
let validate = "ababa abbb ababab"
let result = RegularExpression(regex: regex, validateString: validate)
print(result)
//["abab", "ab", "ababab"]
複製代碼

分支結構 而在多選分支結構(p1|p2)中,此處括號的做用也是不言而喻的,提供了子表達式的全部可能。

要匹配以下的字符串

I love Swift I love Regular Expression

測試以下

let regex = "^I love (Swift|Regular Expression)$"
let validate = "I love Swift"
let result = RegularExpression(regex: regex, validateString: validate)
print(result)
//["I love Swift"]
複製代碼

二、引用分組

這個功能好像swift不支持,有可能我沒找到相應方法,有找到相關支持方法的歡迎提出來。

這是括號一個重要的做用,有了它,咱們就能夠進行數據提取,以及更強大的替換操做。

而要使用它帶來的好處,必須配合使用實現環境的API。

以日期爲例。假設格式是yyyy-mm-dd的,咱們能夠先寫一個簡單的正則

var regex = /\d{4}-\d{2}-\d{2}/;
複製代碼

而後再修改爲括號版的

var regex = /(\d{4})-(\d{2})-(\d{2})/;
複製代碼

好比提取出年、月、日,能夠這麼作:

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
console.log( string.match(regex) ); 
// => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"]
複製代碼

match返回的一個數組,第一個元素是總體匹配結果,而後是各個分組(括號裏)匹配的內容,而後是匹配下標,最後是輸入的文本。(注意:若是正則是否有修飾符g,match返回的數組格式是不同的)。

另外也可使用正則對象的exec方法

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
console.log( regex.exec(string) ); 
// => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"]
複製代碼

同時,也可使用構造函數的全局屬性$1$9來獲取:

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";

regex.test(string); // 正則操做便可,例如
//regex.exec(string);
//string.match(regex);

console.log(RegExp.$1); // "2017"
console.log(RegExp.$2); // "06"
console.log(RegExp.$3); // "12"
複製代碼

好比,想把yyyy-mm-dd格式,替換成mm/dd/yyyy怎麼作?

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, "$2/$3/$1");
console.log(result); 
// => "06/12/2017"
複製代碼

三、反向引用

除了使用相應API來引用分組,也能夠在正則自己裏引用分組。但只能引用以前出現的分組,即反向引用。

仍是以日期爲例。

好比要寫一個正則支持匹配以下三種格式

2016-06-12 2016/06/12 2016.06.12

最早可能想到的正則是:

var regex = /\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // true
複製代碼

其中/和.須要轉義。雖然匹配了要求的狀況,但也匹配"2016-06/12"這樣的數據。

假設咱們想要求分割符先後一致怎麼辦?此時須要使用反向引用:

var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // false
複製代碼

注意裏面的\1,表示的引用以前的那個分組(-|\/|\.)。無論它匹配到什麼(好比-),\1都匹配那個一樣的具體某個字符。

咱們知道了\1的含義後,那麼\2\3的概念也就理解了,即分別指代第二個和第三個分組

括號嵌套怎麼辦

以左括號(開括號)爲準。好比:

var regex = /^((\d)(\d(\d)))\1\2\3\4$/;
var string = "1231231233";
console.log( regex.test(string) ); // true
console.log( RegExp.$1 ); // 123
console.log( RegExp.$2 ); // 1
console.log( RegExp.$3 ); // 23
console.log( RegExp.$4 ); // 3
複製代碼

咱們能夠看看這個正則匹配模式:

  • 第一個字符是數字,好比說1,
  • 第二個字符是數字,好比說2,
  • 第三個字符是數字,好比說3,
  • 接下來的是\1,是第一個分組內容,那麼看第一個開括號對應的分組是什麼,是123,
  • 接下來的是\2,找到第2個開括號,對應的分組,匹配的內容是1,
  • 接下來的是\3,找到第3個開括號,對應的分組,匹配的內容是23,
  • 最後的是\4,找到第3個開括號,對應的分組,匹配的內容是3。

四、 非捕獲分組

以前文中出現的分組,都會捕獲它們匹配到的數據,以便後續引用,所以也稱他們是捕獲型分組。

若是隻想要括號最原始的功能,但不會引用它,即,既不在API裏引用,也不在正則裏反向引用。此時可使用非捕獲分組(?:p),例如本文第一個例子能夠修改成:

var regex = /(?:ab)+/g;
var string = "ababa abbb ababab";
console.log( string.match(regex) ); 
// => ["abab", "ab", "ababab"]
複製代碼

第四章、正則表達式回溯法原理

學習正則表達式,是須要懂點兒匹配原理的。

而研究匹配原理時,有兩個字出現的頻率比較高:「回溯」。

聽起來挺高大上,確實還有不少人對此不明不白的。

所以,本章就簡單扼要地說清楚回溯究竟是什麼東西。

內容包括:

  • 一、沒有回溯的匹配
  • 二、有回溯的匹配
  • 三、常見的回溯形式

一、沒有回溯的匹配

假設咱們的正則是ab{1,3}c,其可視化形式是:

而當目標字符串是abbbc時,就沒有所謂的「回溯」。其匹配過程是:

其中子表達式b{1,3}表示「b」字符連續出現1到3次

二、有回溯的匹配

若是目標字符串是"abbc",中間就有回溯。

圖中第5步有紅顏色,表示匹配不成功。此時b{1,3}已經匹配到了2個字符「b」,準備嘗試第三個時,結果發現接下來的字符是「c」。那麼就認爲b{1,3}就已經匹配完畢。而後狀態又回到以前的狀態(即第6步,與第4步同樣),最後再用子表達式c,去匹配字符「c」。固然,此時整個表達式匹配成功了。圖中的第6步,就是「回溯」。

三、常見的回溯形式

正則表達式匹配字符串的這種方式,有個學名,叫回溯法。回溯法也稱試探法,它的基本思想是:從問題的某一種狀態(初始狀態)出發,搜索從這種狀態出發所能達到的全部「狀態」,當一條路走到「盡頭」的時候(不能再前進),再後退一步或若干步,從另外一種可能「狀態」出發,繼續搜索,直到全部的「路徑」(狀態)都試探過。這種不斷「前進」、不斷「回溯」尋找解的方法,就稱做「回溯法」

本質上就是深度優先搜索算法。其中退到以前的某一步這一過程,咱們稱爲「回溯」。從上面的描述過程當中,能夠看出,路走不通時,就會發生「回溯」。即,嘗試匹配失敗時,接下來的一步一般就是回溯。

貪婪量詞

以前的例子都是貪婪量詞相關的。好比b{1,3},由於其是貪婪的,嘗試可能的順序是從多往少的方向去嘗試。首先會嘗試"bbb",而後再看整個正則是否能匹配。不能匹配時,吐出一個"b",即在"bb"的基礎上,再繼續嘗試。若是還不行,再吐出一個,再試。若是還不行呢?只能說明匹配失敗了

let regex = "\\d{1,3}"
let validate = "12345"
let result = RegularExpression(regex: regex, validateString: validate)
print(result)
//["123", "45"]
複製代碼

惰性量詞

惰性量詞就是在貪婪量詞後面加個問號。表示儘量少的匹配,好比:

let regex = "\\d{1,3}?"
let validate = "12345"
let result = RegularExpression(regex: regex, validateString: validate)
print(result)
//["1", "2", "3", "4", "5"]
複製代碼

分支結構

咱們知道分支也是惰性的,好比/can|candy/,去匹配字符串"candy",獲得的結果是"can",由於分支會一個一個嘗試,若是前面的知足了,後面就不會再試驗了。分支結構,可能前面的子模式會造成了局部匹配,若是接下來表達式總體不匹配時,仍會繼續嘗試剩下的分支。這種嘗試也能夠當作一種回溯。好比正則

第五章、正則表達式的拆分

對於一門語言的掌握程度怎麼樣,能夠有兩個角度來衡量:讀和寫。

不只要求本身能解決問題,還要看懂別人的解決方案。代碼是這樣,正則表達式也是這樣。正則這門語言跟其餘語言有一點不一樣,它一般就是一大堆字符,而沒有所謂「語句」的概念。如何能正確地把一大串正則拆分紅一塊一塊的,成爲了破解「天書」的關鍵。

本章就解決這一問題,內容包括:

  • 一、結構和操做符
  • 二、注意要點
  • 三、案例分析

一、結構和操做符

  • 字面量,匹配一個具體字符,包括不用轉義的和須要轉義的。好比a匹配字符"a"
  • 字符組,匹配一個字符,能夠是多種可能之一,好比[0-9],表示匹配一個數字。也有\d的簡寫形式。另外還有反義字符組,表示能夠是除了特定字符以外任何一個字符,好比[^0-9],表示一個非數字字符,也有\D的簡寫形式。
  • 量詞,表示一個字符連續出現,好比a{1,3}表示「a」字符連續出現3次。另外還有常見的簡寫形式,好比a+表示「a」字符連續出現至少一次
  • 錨點,匹配一個位置,而不是字符。好比^匹配字符串的開頭,又好比\b匹配單詞邊界,又好比(?=\d)表示數字前面的位置。
  • 分組,用括號表示一個總體,好比(ab)+,表示"ab"兩個字符連續出現屢次,也可使用非捕獲分組(?:ab)+
  • 分支,多個子表達式多選一,好比abc|bcd,表達式匹配"abc"或者"bcd"字符子串

這裏,咱們來分析一個正則:

ab?(c|de*)+|fg

  • 一、因爲括號的存在,因此,(c|de*)是一個總體結構。
  • 二、在(c|de*)中,注意其中的量詞*,所以e*是一個總體結構
  • 三、由於分支結構 |優先級最低,所以c是一個總體、而de*是另外一個總體
  • 四、同理,整個正則分紅了 a、b?、(...)+、f、g。而因爲分支的緣由,又能夠分紅ab?(c|de*)+fg這兩部分。

二、注意要點

匹配字符串總體問題

由於是要匹配整個字符串,咱們常常會在正則先後中加上錨字符 ^$

好比要匹配目標字符串"abc"或者"bcd"時,若是一不當心,就會寫成^abc|bcd$

而位置字符和字符序列優先級要比豎槓高,這句正則的意思是開始匹配abc或者結尾匹配bcd

let regex = "^abc|bcd$"
let validate = "abc123456"
let result = RegularExpression(regex: regex, validateString: validate)
print(result)
//["abc"]
複製代碼

正確的寫法應該是^(abc|bcd)$

量詞連綴問題

假設,要匹配這樣的字符串:

  1. 每一個字符爲a、b、c任選其一
  2. 字符串的長度是3的倍數

此時正則不能想固然地寫成^[abc]{3}+$

let regex = "^[abc]{3}+$"
let validate = "abcaaa"
let result = RegularExpression(regex: regex, validateString: validate)
print(result)
//[]
複製代碼

正確的應該寫成^([abc]{3})+$

let regex = "^([abc]{3})+$"
let validate = "abcaaa"
let result = RegularExpression(regex: regex, validateString: validate)
print(result) 
//["abcaaa"]
複製代碼

元字符轉義問題

^ $ . * + ? | \ / ( ) [ ] { } = ! : - ,

let regex = "\\^\\$\\.\\*\\+\\?\\|\\\\\\/\\[\\]\\{\\}\\=\\!\\:\\-\\,"
let validate = "^$.*+?|\\/[]{}=!:-,"
let result = RegularExpression(regex: regex, validateString: validate)
print(result)
//["^$.*+?|\\/[]{}=!:-,"]
複製代碼

須要用\\轉義

匹配「[abc]」和「{3,5}」

let regex = "\\[abc]"
let validate = "[abc]"
let result = RegularExpression(regex: regex, validateString: validate)
print(result)
//["[abc]"]
複製代碼

只須要在第一個方括號轉義便可,由於後面的方括號構不成字符組,正則不會引起歧義,天然不須要轉義。

文章轉載:JS正則表達式完整教程(略長)

相關文章
相關標籤/搜索