正則表達式多選結構的順序

正則表達式多選結構的順序

先看一道編程題

從一段只包含[.,0-9]字符的字符串中提取出所有可能的IPv4地址。java

IPv4地址由十進制數和點來表示,每一個地址包含4個十進制數,其範圍爲0-255,好比172.16.254.1;同時,4個十進制數不會以0開頭,好比172.16.254.01是不合法的。正則表達式

現輸入一段文本str="1.1.1111.16..172.16.254.1.1",要求用程序答出該字符串全部子串可能造成的IPv4地址。express

不少人會想到,IPv4的正則表達式我可熟悉啦,確定能快速完成!因而從網上搜索到IPv4的正則表達式,寫出瞭如下代碼:編程

// Java語言
import java.util.*;
import java.util.regex.*;

class Solution {
    private static final Pattern IPV4_PATTERN =
            Pattern.compile("((([1-9][0-9]?)|(1[0-9]{2})|(2[0-4]\\d)|(25[0-5])|0)\\.){3}" +
                    "(([1-9][0-9]?)|(1[0-9]{2})|(2[0-4]\\d)|(25[0-5])|0)");

    public Set<String> findAllIpv4(String input) {
        Set<String> s = new TreeSet<String>();
        Matcher m = IPV4_PATTERN.matcher(input);
        int from = 0;
        while (m.find(from)) {
            s.add(input.substring(m.start(), m.end()));
            from++;
        }
        return s;
    }
}

咱們看完這一段代碼,能夠肯定的是:1. 正則表達式沒問題,正確的IPV4地址能夠用它來驗證;2. Java Regex API用法也沒有太大問題,基本符合預期。code

輸入:0.0.0.255
輸出:[0.0.0.25]

但這段代碼的結果是不正確的,正確輸出是[0.0.0.2, 0.0.0.25, 0.0.0.255],緣由就出在正則中多選結構|的用法上。字符串

多選結構(Alternation)

多選結構在不一樣的正則引擎中,工做原理是大相徑庭的。在傳統型NFA引擎中,會按照從左到右的順序檢查表達式中的多選分支,一旦能夠匹配完成,其餘的多選分支就不會嘗試了1input

以上節的正則表達式爲例,咱們單獨摘出每一個十進制數的表達式(([1-9][0-9]?)|(1[0-9]{2})|(2[0-4]\\d)|(25[0-5])|0),它會以以下順序進行匹配:string

1. ([1-9][0-9]?)      # 一位數或兩位數
2. (1[0-9]{2})        # 位於區間[100-199]的三位數
3. (2[0-4]\\d)        # 位於區間[200-249]的三位數
4. (25[0-5])          # 位於區間[250-255]的三位數
5. 0                  # 0

那麼在針對輸入字符串0.0.0.255的匹配過程當中,在進行第四個十進制數255的匹配時,會優先計算表達式([1-9][0-9]?),這樣能夠匹配到252,而?(question mark)在正則中是一個貪婪量詞2,所以僅會留下25,因此最終咱們看到了運行結果是[0.0.0.25]io

以這些知識爲前提,咱們能夠經過優先匹配多位數字,手工解析少許數字的方式獲得正確的解答程序,這樣作的話,正則表達式須要作一些調整,將多位數匹配的多選分支放在前面,即(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d),代碼以下:ast

// Java語言
import java.util.*;
import java.util.regex.*;

class Solution {
    private static final Pattern IPV4_PATTERN =
            Pattern.compile("((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)\\.){3}" +
                    "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)");

    public Set<String> findAllIpv4(String input) {
        Set<String> s = new TreeSet<String>();
        Matcher m = IPV4_PATTERN.matcher(input);
        int from = 0;
        int lastDotIdx = 0;
        String sub = null;
        while (m.find(from)) {
            sub = input.substring(m.start(), m.end());
            s.add(sub);
            lastDotIdx = sub.lastIndexOf('.');
            if (lastDotIdx == sub.length() - 3) {
                s.add(sub.substring(0, sub.length() - 1));
            } else if (lastDotIdx == sub.length() - 4) {
                s.add(sub.substring(0, sub.length() - 1));
                s.add(sub.substring(0, sub.length() - 2));
            }
            from++;
        }
        return s;
    }
}

  1. Friedl, J. E. (2006). Mastering regular expressions. " O'Reilly Media, Inc.", p174-p175.

  2. Friedl, J. E. (2006). Mastering regular expressions. " O'Reilly Media, Inc.", p142.

相關文章
相關標籤/搜索