正则表达式 - 检查输入是否仍有机会匹配

Regex - check if input still has chances to become matching

提问人:Adam Pietrasiak 提问时间:3/18/2014 最后编辑:Adam Pietrasiak 更新时间:7/19/2020 访问量:7063

问:

我们有这样的正则表达式:

var regexp = /^one (two)+ three/;

因此,只有字符串 like or or etc. 才能匹配它。"one two three""one two three four""one twotwo three"

但是,如果我们有像这样的字符串

"one "- 仍然“有希望”,也许很快就会匹配

但是这个字符串:无论我们做什么,都永远不会匹配。"one three"

有没有办法检查给定的字符串是否有机会匹配?

当我想推荐所有以给定输入开头的选项时,我在写作过程中需要它来提供一些提示(我使用的正则表达式很长,我不想真的弄乱它们)。


换句话说 - 我想检查字符串是否在检查期间结束,并且没有遇到任何“不匹配”的情况。

换句话说 - 答案将是不匹配的内部原因。如果 reason 是字符串的末尾 - 那么它将是 promissing。但是,我不知道有任何方法可以检查为什么某些字符串不匹配

JavaScript 正则表达式

评论

1赞 Jason Aller 3/18/2014
您是否正在寻找一个函数,该函数将以数字方式指示部分匹配的完整性,或者在输入不匹配时指示不匹配?所以它会回来(4/13),但它会回来吗?"one ".3076923"one three"-1
0赞 Jerry 3/19/2014
似乎最好的方法是编写一个完整的库来做到这一点,并满足 JS 的正则表达式可以提供的所有不同场景......
5赞 Ulugbek Umirov 3/19/2014
非常有趣的问题。您可以按以下形式重写表达式:检查文本是否有希望。如果你的表达式并不复杂,我相信可以从主正则表达式中自动派生有前途的正则表达式。^o(?:n(?:e(?: (?:(?:two)*(?:t(?:w(?:o(?: (?:t(?:h(?:r(?:e(?:e.*?)?)?)?)?)?)?)?)?)?)?)?)?)?$
1赞 Alan Moore 3/19/2014
你嫁给了 JavaScript 吗?这个问题经常出现,除非将功能内置到正则表达式实现中,否则这是不可能的。它通常被称为部分匹配,据我所知,Java 和 Boost 是唯一支持它的风格。
0赞 Lucas Trzesniewski 1/11/2017
@AlanMoore仅供参考 PCRE 也支持此功能

答:

-1赞 Niet the Dark Absol 3/18/2014 #1

这实际上是一个非常有趣的问题。

就个人而言,我会使用 RegExp 构造函数来做到这一点,因此可以分解查询:

var input = "one ";
var query = "^one two three";
var q_len = query.length;
var match;
for( var i=1; i<=q_len; i++) {
    match = input.match(new RegExp(query.substr(0,i));
    if( !match) {alert("Pattern does NOT match"); break;}
    if( match[0].length == input.length) {alert("Input is promising!"); break;}
    // else continue to the next iteration
}

显然,您可以预先处理“完全匹配”的情况,以避免整个循环问题。如果整个模式与输入匹配,那么你就很好。

编辑:我刚刚意识到这对团体和其他东西不起作用。由于格式不正确的正则表达式,它会失败,但我希望它可以作为您查询的基础。

评论

0赞 Adam Pietrasiak 3/18/2014
我在问题中编辑了正则表达式,所以它也使用特殊字符。它还能用吗?
0赞 Niet the Dark Absol 3/18/2014
@Kluska000我已经编辑了我的答案;)不幸的是,如果您有任何类型的分组,它将不起作用,因为生成的正则表达式格式不正确并导致错误。您可以将循环的内容包装在一个块中,以简单地跳过正则表达式无效的迭代。try..catch
0赞 Adam Pietrasiak 3/18/2014
假设所有查询或其中的大部分都被分组,这种“try catch”将在大部分时间被捕获,因此下一个工作查询将有“大步骤”。
0赞 Niet the Dark Absol 3/18/2014
是的。我不认为这个问题有任何通用解决方案,而无需大量使用解析器和其他东西。
0赞 Robin 3/18/2014 #2

编辑:在您编辑帖子澄清后,此答案可能与您的具体问题无关。留在这里供参考。


在正则表达式本身中,关于一旦你知道你什么也找不到,就会迅速失败的问题:

正则表达式中的锚点意味着一旦正则表达式引擎到达 of ,它将停止寻找匹配项,并且不会尝试在第二个字符上开始匹配。在这种情况下,我认为没有比这更好的了(但它已经是线性复杂性,所以还不错)。hthree

在其他情况下,我相信你有一些常用的最佳实践需要学习,以便在你知道再也找不到匹配项时尽快失败。

如果你还不知道所有格量词,你可以看看它们,但还有许多其他技巧......

0赞 rgthree 3/18/2014 #3

你最初的问题只是测试一个字符串在另一个字符串中的位置,特别是开始。最快的方法是在匹配字符串上使用,然后使用 .我已经更新了我的原始答案以反映这一点:substrindexOf

function check(str){
  return 'one two three'.substr(0, str.length) === str;
};
console.log(check('one'));           // true
console.log(check('one two'));       // true
console.log(check('one two three')); // true
console.log(check('one three'));     // false

如果你需要不区分大小写,那么简单地匹配和输入字符串仍然是最快的。(如果有兴趣,这里有一个 substrindexOfRegExp 测试的 jsperf 用于字符串的开头,不区分大小写:http://jsperf.com/substr-vs-indexof-vs-regextoLowerCase

评论

0赞 Adam Pietrasiak 3/18/2014
它适用于更复杂的字符(特殊字符、闭包等)吗?
1赞 Niet the Dark Absol 3/18/2014
@Kluska000 不,不会。这处理了一个非常具体的情况,其中您的正则表达式实际上是 ,在这种情况下,您不妨这样做。出于这个原因,我反对这个答案。^something literal herereturn rgxStr.substr(0,str.length) == str
0赞 rgthree 3/18/2014
@NiettheDarkAbsol哈,是的。在考虑清楚之前回答得太快了。我已经更新了我的答案,以反映答案的真实性质。谢谢。
-2赞 Nate 3/18/2014 #4

不确定是否有办法在不制作极其复杂的模式的情况下使用正则表达式执行此操作。但是,如果您只是检查字符串,则可以执行以下操作:

function matchPartialFromBeginning(pattern, value) {
    if(pattern.length == value.length)
        return pattern == value;
    if(pattern.length > value.length)
        return pattern.substr(0, value.length) == value;
    return false;
}
-1赞 elixenide 3/18/2014 #5

下面是 jsFiddle 中的简单概念证明。基本上,您只需反向循环访问建议的正则表达式,并查找最长的子匹配项。

注意:这有一个已知问题,因为它并不总是很好地处理组。例如,它会说根本不匹配,而它应该说还有希望。这可以通过使用以下行变得更有创意来解决:foo bar bfoo( bar)+

temp_re = new RegExp(regex_part+'$'); // make sure this partial match ends in a piece that could still match the entire regex

基本上,您需要解析部分正则表达式以查看它是否以组结尾,然后递归检查与该结束组的部分匹配项。这相当复杂,所以我不是为了我的演示而讨论它。

JavaScript(仅将 jQuery 用于演示目的):

var strings = {
    matches: "Matches!",
    stillhope: "There's still hope...",
    mismatch: "Does not match",
    empty: "No text yet"
};

// Object to handle watching for partial matches
function PartialRegexMonitor(regex, input_id) {
    var self = this;
    this.relen = regex.length;
    $('body').on('keyup', '#'+input_id, function() {
         self.update_test_results(regex, input_id);
    });
}

PartialRegexMonitor.prototype.update_test_results = function(regex, input_id) {
    var input = $('#'+input_id).val(),
        matches = find_partial_matches(regex, input),
        span = $('#'+input_id).siblings('.results');
    span.removeClass('match');
    span.removeClass('stillhope');
    span.removeClass('mismatch');
    span.removeClass('empty');
    span.addClass(matches.type)
        .html(strings[matches.type]);
}

// Test a partial regex against a string
function partial_match_tester(regex_part, str) {
    var matched = false;
    try {
        var re = new RegExp(regex_part, 'g'),
            matches = str.match(re),
            match_count = matches.length,
            temp_re;
        for(var i = 0; i < match_count; i++) {
            temp_re = new RegExp(regex_part+'$'); // make sure this partial match ends in a piece that could still match the entire regex
            matched = temp_re.test(str);
            if(matched) break;
        }
    }
    catch(e) {
    }
    return matched;
}

// Find the longest matching partial regex
function find_partial_matches(regex, str) {
    var relen = regex.length,
        matched = false,
        matches = {type: 'mismatch',
                   len: 0},
        regex_part = '';
    if(str.length == 0) {
        matches.type = 'empty';
        return matches;
    }

    for(var i=relen; i>=1; i--) {
        if(i==1 && str[0] == '^') { // don't allow matching against only '^'
            continue;
        }
        regex_part = regex.substr(0,i);
        // replace {\d}$ with {0,\d} for testing
        regex_part = regex_part.replace(/\{(\d)\}$/g, '{0,$1}');

        matched = partial_match_tester(regex_part, str);
        if(matched) {
            matches.type = (i==relen ? 'matches' : 'stillhope');
            console.log(matches.type + ": "+regex.substr(0,i)+" "+str);
            matches.len = i;
            break;
        }
    }
    return matches;
}

// Demo
$(function() {
    new PartialRegexMonitor('foo bar', 'input_0');
    new PartialRegexMonitor('^foo bar$', 'input_1');
    new PartialRegexMonitor('^fo+(\\s*b\\S[rz])+$', 'input_2');
    new PartialRegexMonitor('^\\d{3}-\\d{3}-\\d{4}$', 'input_3');
});

演示的 HTML:

<p>
Test against <span class="regex">foo bar</span>:<br/>
    <input type="text" id="input_0" />
    <span class="results empty">No text yet</span>
</p>
<p>
    Test against <span class="regex">^foo bar$</span>:<br/>
    <input type="text" id="input_1" />
    <span class="results empty">No text yet</span>
</p>
<p>
    Test against <span class="regex">^fo+(\s*b\S[rz])+$</span> (e.g., "foo bar", "foo baz", "foo bar baz"):<br/>
    <input type="text" id="input_2" />
    <span class="results empty">No text yet</span>
</p>
<p>
    Test against <span class="regex">^\d{3}-\d{3}-\d{4}$</span>:<br/>
    <input type="text" id="input_3" />
    <span class="results empty">No text yet</span>
</p>

演示的 CSS

.empty {
    background-color: #eeeeee;
}
.matches {
    background-color: #ccffcc;
    font-weight: bold;
}
.stillhope {
    background-color: #ccffff;
}
.mismatch {
    background-color: #ffcccc;
    font-weight: bold;
}
.regex {
    border-top:1px solid #999;
    border-bottom:1px solid #999;
    font-family: Courier New, monospace;
    background-color: #eee;
    color: #666;
}

演示中的示例屏幕截图

enter image description here

评论

0赞 Jerry 3/19/2014
但这也意味着它会在我认为不应该的时候通过并返回真实。foo bad
0赞 elixenide 3/19/2014
是的,我想我误读了这个问题。我将更新这个答案。
0赞 Adam Pietrasiak 3/19/2014
jsfiddle.net/He5tp/9我在玩你的解决方案。最后一个不起作用。我正在考虑修改正则表达式,以便将 foo 替换为 foo|fo|f fingind 第一个最长的可能匹配结果。
0赞 elixenide 3/19/2014
这应该仍然有效。就像我说的,当它应该在最后匹配一个组时,它可能需要调整,但希望它能给你一个起点。:)
16赞 Andacious 3/19/2014 #6

我以前使用过的另一个有趣的选项是 OR 符号预期的每个字符。这可能不适用于每种情况,但对于您正在查看特定字符并且需要对每个字符进行部分匹配的情况,这可行。$

例如(在 Javascript 中):

var reg = /^(o|$)(n|$)(e|$)(\s|$)$/;

reg.test('')      -> true;
reg.test('o')     -> true;
reg.test('on')    -> true;
reg.test('one')   -> true;
reg.test('one ')  -> true;
reg.test('one t') -> false;
reg.test('x')     -> false;
reg.test('n')     -> false;
reg.test('e')     -> false;
reg.test(' ')     -> false;

虽然这不是最漂亮的正则表达式,但它是可重复的,所以如果你出于某种原因需要动态生成它,你知道一般的模式。

同样的模式也可以应用于整个单词,这可能没有那么有用,因为他们无法一个接一个地输入来达到这些点。

var reg = /^(one|$)(\stwo|$)$/;

reg.test('')        -> true;
reg.test('one')     -> true;
reg.test('one ')    -> false;
reg.test('one two') -> true;

评论

2赞 Alan Moore 3/19/2014
+1.它不能解决 OP 的问题,但它是一种巧妙的技术。在它确实适用的情况下,它可能是救命稻草。
3赞 Lucas Trzesniewski 1/12/2017
我的回答是基于你的想法,希望你不介意:)
1赞 Lars Holdaas 12/17/2020
这是一个很好的答案。多么巧妙的技术
1赞 Don Hatch 5/19/2021
这真是太聪明了。我正要卷起袖子,在这里编写我的答案:stackoverflow.com/questions/12015612/...... (顺便说一句,我真的为此感到自豪)然后我发现了@LucasTrzesniewski上面的实现,我想“为什么这么短??这简直简单得离谱。太棒了。
0赞 Don Hatch 5/19/2021
@AlanMoore 它以什么方式不能解决OP的问题?
0赞 Adam Pietrasiak 3/19/2014 #7

根据@Andacious答案,我制作了自己的,更高级一点,它似乎有效。

我制作了修改正则表达式的方法,使其接受有希望的答案。

它用 so 替换任何文字字符,从而成为寻找匹配的字母或输入结尾。"(?:OLD_LETTER|$)"k(?:k|$)

它还会查找不要像 {1,2} 那样替换的部件,并将它们保持原样。

我敢肯定它不完整,但是添加新的检查规则和主要技巧非常容易,似乎在任何情况下都可以作为字符串匹配的结尾并且不要继续,所以基本上我们需要对主正则表达式进行这样的修改,任何文字字符或字符组都将有替代,并且每个语法(有时似乎也有字面字符)都不能被破坏。any_sign or end of input|$

RegExp.prototype.promising = function(){
    var source = this.source;
    var regexps = {
        replaceCandidates : /(\{[\d,]\})|(\[[\w-]+\])|((?:\\\w))|([\w\s-])/g,   //parts of regexp that may be replaced
        dontReplace : /\{[\d,]\}/g,     //dont replace those
    }

    source =  source.replace(regexps.replaceCandidates, function(n){ 
        if ( regexps.dontReplace.test(n) ) return n;
        return "(?:" + n + "|$)";
    });


    source = source.replace(/\s/g, function(s){
        return "(?:" + s + "|$)";
    });

    return new RegExp(source);

}

在 jsFiddle 上测试

44赞 Lucas Trzesniewski 1/11/2017 #8

这是一个称为部分匹配的正则表达式功能,它在多个正则表达式引擎(如 PCRE、Boost、Java)中可用,但在 JavaScript 中不可用

Andacious的回答显示了克服这个限制的一个非常好的方法,我们只需要自动化它。

井。。。接受挑战:)

幸运的是,JavaScript 的正则表达式特性集非常有限,语法简单,因此我根据 MDN 上列出的特性为这项任务编写了一个简单的解析器和动态转换。此代码已更新为处理 ES2018 功能。

几个兴趣点:

  • 这将生成一个正则表达式,该正则表达式几乎总是与空字符串匹配。因此,当 is 的结果或第一个元素为空字符串的数组时,将发生部分匹配失败execnull
  • 负面展望保持原样。我相信这是正确的做法。匹配失败的唯一方法是通过它们(即在正则表达式中放置 a)和锚点 ( 和 )。回溯(正面和负面)也保持原样。(?!)^$
  • 解析器假定输入模式有效:您首先不能从无效模式创建对象。如果引入新的正则表达式功能,这可能会在将来中断。RegExp
  • 此代码将无法正确处理反向引用:例如,不会产生部分匹配。^(\w+)\s+\1$hello hel

RegExp.prototype.toPartialMatchRegex = function() {
    "use strict";
    
    var re = this,
        source = this.source,
        i = 0;
    
    function process () {
        var result = "",
            tmp;

        function appendRaw(nbChars) {
            result += source.substr(i, nbChars);
            i += nbChars;
        };
        
        function appendOptional(nbChars) {
            result += "(?:" + source.substr(i, nbChars) + "|$)";
            i += nbChars;
        };

        while (i < source.length) {
            switch (source[i])
            {
                case "\\":
                    switch (source[i + 1])
                    {
                        case "c":
                            appendOptional(3);
                            break;
                            
                        case "x":
                            appendOptional(4);
                            break;
                            
                        case "u":
                            if (re.unicode) {
                                if (source[i + 2] === "{") {
                                    appendOptional(source.indexOf("}", i) - i + 1);
                                } else {
                                    appendOptional(6);
                                }
                            } else {
                                appendOptional(2);
                            }
                            break;

                        case "p":
                        case "P":
                            if (re.unicode) {
                                appendOptional(source.indexOf("}", i) - i + 1);
                            } else {
                                appendOptional(2);
                            }
                            break;

                        case "k":
                            appendOptional(source.indexOf(">", i) - i + 1);
                            break;
                            
                        default:
                            appendOptional(2);
                            break;
                    }
                    break;
                    
                case "[":
                    tmp = /\[(?:\\.|.)*?\]/g;
                    tmp.lastIndex = i;
                    tmp = tmp.exec(source);
                    appendOptional(tmp[0].length);
                    break;
                    
                case "|":
                case "^":
                case "$":
                case "*":
                case "+":
                case "?":
                    appendRaw(1);
                    break;
                    
                case "{":
                    tmp = /\{\d+,?\d*\}/g;
                    tmp.lastIndex = i;
                    tmp = tmp.exec(source);
                    if (tmp) {
                        appendRaw(tmp[0].length);
                    } else {
                        appendOptional(1);
                    }
                    break;
                    
                case "(":
                    if (source[i + 1] == "?") {
                        switch (source[i + 2])
                        {
                            case ":":
                                result += "(?:";
                                i += 3;
                                result += process() + "|$)";
                                break;
                                
                            case "=":
                                result += "(?=";
                                i += 3;
                                result += process() + ")";
                                break;
                                
                            case "!":
                                tmp = i;
                                i += 3;
                                process();
                                result += source.substr(tmp, i - tmp);
                                break;

                            case "<":
                                switch (source[i + 3])
                                {
                                    case "=":
                                    case "!":
                                        tmp = i;
                                        i += 4;
                                        process();
                                        result += source.substr(tmp, i - tmp);
                                        break;

                                    default:
                                        appendRaw(source.indexOf(">", i) - i + 1);
                                        result += process() + "|$)";
                                        break;        
                                }
                                break;
                        }
                    } else {
                        appendRaw(1);
                        result += process() + "|$)";
                    }
                    break;
                    
                case ")":
                    ++i;
                    return result;
                    
                default:
                    appendOptional(1);
                    break;
            }
        }
        
        return result;
    }
    
    return new RegExp(process(), this.flags);
};






// Test code
(function() {
    document.write('<span style="display: inline-block; width: 60px;">Regex: </span><input id="re" value="^one (two)+ three"/><br><span style="display: inline-block; width: 60px;">Input: </span><input id="txt" value="one twotw"/><br><pre id="result"></pre>');
    document.close();

    var run = function() {
        var output = document.getElementById("result");
        try
        {
            var regex = new RegExp(document.getElementById("re").value);
            var input = document.getElementById("txt").value;
            var partialMatchRegex = regex.toPartialMatchRegex();
            var result = partialMatchRegex.exec(input);
            var matchType = regex.exec(input) ? "Full match" : result && result[0] ? "Partial match" : "No match";
            output.innerText = partialMatchRegex + "\n\n" + matchType + "\n" + JSON.stringify(result);
        }
        catch (e)
        {
            output.innerText = e;
        }
    };

    document.getElementById("re").addEventListener("input", run);
    document.getElementById("txt").addEventListener("input", run);
    run();
}());

我测试了一下,它似乎工作正常,如果您发现任何错误,请告诉我。

评论

1赞 Lucas Trzesniewski 8/7/2019
@Patrick哦,好吧,对不起。如果你在末尾添加点,它仍然是一个完整的匹配,因为你的模式没有锚定。将模式包裹在 和 之间以锚定它。无论如何,这可能是您在寻找部分匹配时一直想要的。^$
1赞 Tomasz Pala 7/16/2020
仅供参考,此代码在步入命名捕获组时进入无限循环。例如,这是我使用命名反向引用的正则表达式的一部分:(?<city>KAT|BYT)-[A-Z]{2,}/00\d{4}/%y/\k<city>
1赞 Lucas Trzesniewski 7/16/2020
@TomaszPala感谢您提供的信息!当我编写该代码时,JS 中并不存在命名捕获组,这解释了为什么它不起作用。我不确定我是否应该更新此答案以处理新功能(并将将来继续这样做)或保持原样。此外,反向引用没有得到正确处理(不幸的是)。
1赞 Lucas Trzesniewski 7/19/2020
@TomaszPala,我更新了代码以处理新的 ES2018 功能。您的模式现在应该被处理了,但反向引用除外。
1赞 Tomasz Pala 7/21/2020
我必须说,结果给我留下了深刻的印象!命名的 backref 必须以完整捕获的形式输入,但对于这种简短的插入式解决方案来说,这是完全可以理解的:) 荣誉!
0赞 James Wilkins 1/11/2017 #9

仍然不是 100% 确定您要求什么,但您也可以嵌套它们,如下所示:

var regexp = /^(one)+((\s+two)+((\s+three)+((\s+four)+)?)?)?$/;

比赛:

  • 一二二
  • 一二二三
  • 一二二三三四

不匹配:

  • 一三
2赞 Nathan 7/18/2019 #10

我发现了一个 npm 包,其中包含具有增量正则表达式匹配的 RegEx 的 JavaScript 实现:https://www.npmjs.com/package/incr-regex-package。看起来值得一试。它可以报告给定输入的 、 和结果。DONEMOREMAYBEFAILED

这里还有一个 React 输入组件的实现示例:https://www.npmjs.com/package/react-maskedinput。它用于提供与正则表达式库交互的更以用户输入为中心的视图。{RXInputMask} from incr-regex-package