C# 从字符串的源列表动态生成最短正则表达式

C# 从字符串的源列表动态生成最短正则表达式,c#,regex,linq,lambda,combinatorics,C#,Regex,Linq,Lambda,Combinatorics,我有一堆SKU(库存单位),它们表示一系列字符串,我想创建一个正则表达式来匹配这些字符串 例如,如果我有SKU: var skus = new[] { "BATPAG003", "BATTWLP03", "BATTWLP04", "BATTWSP04", "SPIFATB01" }; …我想自动生成正则表达式来识别任何一个SKU 我知道我可以简单地执行“BATPAG003 | BATTWLP03 | BATTWLP04 | BATTWSP04 | SPIFATB01”,但SKU列表可能相当长,

我有一堆SKU(库存单位),它们表示一系列字符串,我想创建一个正则表达式来匹配这些字符串

例如,如果我有SKU:

var skus = new[] { "BATPAG003", "BATTWLP03", "BATTWLP04", "BATTWSP04", "SPIFATB01" };
…我想自动生成正则表达式来识别任何一个SKU

我知道我可以简单地执行
“BATPAG003 | BATTWLP03 | BATTWLP04 | BATTWSP04 | SPIFATB01”
,但SKU列表可能相当长,我想压缩生成的正则表达式,使其看起来像
“BAT(PAG003 | TW(LP0(3 | 4)| SP04))| SPIFATB01”

这是一个组合数学练习。我想生成所有可能的正则表达式,以匹配我的任何输入字符串,并且认为最短的可能是最好的

例如,我可以制作以下任何一种:

BATPAG003|BATTWLP03|BATTWLP04|BATTWSP04|SPIFATB01
BAT(PAG003|TW(LP0(3|4)|SP04))|SPIFATB01
BAT(PAG003|TW(LP(03|04)|SP04))|SPIFATB01
B(ATPAG003|ATTW(LP0(3|4)|ATSP04))|SPIFATB01
任何一个都可以,但最短的可能是我会选择的

首先,我尝试实现此功能:

Func<IEnumerable<string>, IEnumerable<string>> regexify = null;
regexify = xs =>
    from n in Enumerable.Range(1, 10)
    let g = xs.ToArray().Where(s => !String.IsNullOrWhiteSpace(s)).GroupBy(x => new String(x.Take(n).ToArray()), x => new String(x.Skip(n).ToArray()))
    let parts  = g.SelectMany(x => x.Count() > 1 ? regexify(x).Select(y => x.Key + "(" + String.Join("|", y) + ")") : new [] { x.Key + String.Join("", x) })
    let regex = String.Join("|", parts)
    orderby regex.Length
    select regex;

感觉非常接近,我想我有点做错了。

如果每个SKU id都有相同的长度,这就行了

// ...
string regexStr = Calculate(skus);
// ...

public static string Calculate(IEnumerable<string> rest) {
    if (rest.First().Length > 0) {
        string[] groups = rest.GroupBy(r => r[0])
            .Select(g => g.Key + Calculate(g.Select(e => e.Substring(1))))
            .ToArray();
        return groups.Length > 1 ? "(" + string.Join("|", groups) + ")" : groups[0];
    } else {
        return string.Empty;
    }
}
/。。。
字符串regexStr=计算(SKU);
// ...
公共静态字符串计算(IEnumerable rest){
如果(rest.First().Length>0){
string[]groups=rest.GroupBy(r=>r[0])
.Select(g=>g.Key+Calculate(g.Select(e=>e.Substring(1)))
.ToArray();
返回groups.Length>1?”(“+string.Join(“|”,groups)+”):groups[0];
}否则{
返回字符串。空;
}
}

这是我最后得出的结论:

var skus = new[] { "BATPAG003", "BATTWLP03", "BATTWLP04", "BATTWSP04", "SPIFATB01" };

Func<IEnumerable<IGrouping<string, string>>, IEnumerable<string>> regexify = null;

Func<IEnumerable<string>, IEnumerable<string>> generate =
    xs =>
        from n in Enumerable.Range(2, 20)
        let g = xs.GroupBy(x => new String(x.Take(n).ToArray()), x => new String(x.Skip(n).ToArray()))
        where g.Count() != xs.Count()
        from r in regexify(g)
        select r;

regexify = gxs =>
{
    if (!gxs.Any())
    {
        return new [] { "" };
    }
    else
    {
        var rs = regexify(gxs.Skip(1)).ToArray();
        return
            from f in gxs.Take(1)
            from z in new [] { String.Join("|", f) }.Concat(f.Count() > 1 ? generate(f) : Enumerable.Empty<string>())
            from r in rs
            select f.Key + (f.Count() == 1 ? z : $"({z})") + (r != "" ? "|" + r : "");
    }
};
…我得到了这个结果:

BAT(PAG003|TW(LP0(3|4)|SP04))|SPIFATB01 BAT(PAG003|TWLP0(3|4)|TWSP04)|SPIFATB01 BA(TPAG003|TTW(LP0(3|4)|SP04))|SPIFATB01 BAT(PAG003|TW(LP(03|04)|SP04))|SPIFATB01 BAT(PAG003|TW(LP03|LP04|SP04))|SPIFATB01 BAT(PAG003|TWLP(03|04)|TWSP04)|SPIFATB01 BATPAG003|BATTW(LP0(3|4)|SP04)|SPIFATB01 BA(TPAG003|TT(WLP0(3|4)|WSP04))|SPIFATB01 BA(TPAG003|TTW(LP(03|04)|SP04))|SPIFATB01 BA(TPAG003|TTW(LP03|LP04|SP04))|SPIFATB01 BA(TPAG003|TTWLP0(3|4)|TTWSP04)|SPIFATB01 BAT(PAG003|TWL(P0(3|4))|TWSP04)|SPIFATB01 BAT(PAG003|TWL(P03|P04)|TWSP04)|SPIFATB01 BATPAG003|BATT(WLP0(3|4)|WSP04)|SPIFATB01 BATPAG003|BATTW(LP(03|04)|SP04)|SPIFATB01 BATPAG003|BATTW(LP03|LP04|SP04)|SPIFATB01 BA(TPAG003|TT(WLP(03|04)|WSP04))|SPIFATB01 BA(TPAG003|TTWLP(03|04)|TTWSP04)|SPIFATB01 BAT(PAG003|TWLP03|TWLP04|TWSP04)|SPIFATB01 BATPAG003|BATT(WLP(03|04)|WSP04)|SPIFATB01 BA(TPAG003|TT(WL(P0(3|4))|WSP04))|SPIFATB01 BA(TPAG003|TT(WL(P03|P04)|WSP04))|SPIFATB01 BA(TPAG003|TT(WLP03|WLP04|WSP04))|SPIFATB01 BA(TPAG003|TTWL(P0(3|4))|TTWSP04)|SPIFATB01 BA(TPAG003|TTWL(P03|P04)|TTWSP04)|SPIFATB01 BATPAG003|BATT(WL(P0(3|4))|WSP04)|SPIFATB01 BATPAG003|BATT(WL(P03|P04)|WSP04)|SPIFATB01 BATPAG003|BATT(WLP03|WLP04|WSP04)|SPIFATB01 BATPAG003|BATTWLP0(3|4)|BATTWSP04|SPIFATB01 BATPAG003|BATTWLP(03|04)|BATTWSP04|SPIFATB01 BA(TPAG003|TTWLP03|TTWLP04|TTWSP04)|SPIFATB01 BATPAG003|BATTWL(P0(3|4))|BATTWSP04|SPIFATB01 BATPAG003|BATTWL(P03|P04)|BATTWSP04|SPIFATB01 BAT(PAG003 | TW(LP0(3 | 4)| SP04))| SPIFATB01 BAT(PAG003 | TWLP0(3 | 4)| TWSP04)| SPIFATB01 BA(TPAG003 | TTW(LP0(3 | 4)| SP04))| SPIFATB01 BAT(PAG003 | TW(LP(03 | 04)| SP04))| SPIFATB01 BAT(PAG003 | TW(LP03 | LP04 | SP04))| SPIFATB01 BAT(PAG003 | TWLP(03 | 04)| TWSP04)| SPIFATB01 BATPAG003 | BATTW(LP0(3 | 4)| SP04)| SPIFATB01 BA(TPAG003 | TT(WLP0(3 | 4)| WSP04))| SPIFATB01 BA(TPAG003 | TTW(LP(03 | 04)| SP04))| SPIFATB01 BA(TPAG003 | TTW(LP03 | LP04 | SP04))| SPIFATB01 BA(TPAG003 | TTWLP0(3 | 4)| TTWSP04)| SPIFATB01 BAT(PAG003 | TWL(P0(3 | 4))| TWSP04)| SPIFATB01 BAT(PAG003 | TWL(P03 | P04)| TWSP04)| SPIFATB01 BATPAG003 | BATT(WLP0(3 | 4)| WSP04)| SPIFATB01 BATPAG003 | BATTW(LP(03 | 04)| SP04)| SPIFATB01 BATPAG003 | BATTW(LP03 | LP04 | SP04)| SPIFATB01 BA(TPAG003 | TT(WLP(03 | 04)| WSP04))| SPIFATB01 BA(TPAG003 | TTWLP(03 | 04)| TTWSP04)| SPIFATB01 BAT(PAG003 | TWLP03 | TWLP04 | TWSP04)| SPIFATB01 BATPAG003 | BATT(WLP(03 | 04)| WSP04)| SPIFATB01 BA(TPAG003 | TT(WL(P0(3 | 4))| WSP04))| SPIFATB01 BA(TPAG003 | TT(WL(P03 | P04)| WSP04))| SPIFATB01 BA(TPAG003 | TT(WLP03 | WLP04 | WSP04))| SPIFATB01 BA(TPAG003 | TTWL(P0(3 | 4))| TTWSP04)| SPIFATB01 BA(TPAG003 | TTWL(P03 | P04)| TTWSP04)| SPIFATB01 BATPAG003 | BATT(WL(P0(3 | 4))| WSP04 | SPIFATB01 BATPAG003 | BATT(WL(P03 | P04)| WSP04)| SPIFATB01 BATPAG003 | BATT(WLP03 | WLP04 | WSP04)| SPIFATB01 BATPAG003 | BATTWLP0(3 | 4)| BATTWSP04 | SPIFATB01 BATPAG003 | BATTWLP(03 | 04)| BATTWSP04 | SPIFATB01 BA(TPAG003 | TTWLP03 | TTWLP04 | TTWSP04)| SPIFATB01 BATPAG003 | BATTWL(P0(3 | 4))| BATTWSP04 | SPIFATB01 BATPAG003 | BATTWL(P03 | P04)| BATTWSP04 | SPIFATB01 我的方法唯一的问题是计算时间。我的一些源代码列表有近100个SKU。有些运行的时间比我愿意等待的时间长,必须将其分解为更小的块,然后手动连接。

获取所有sku的完整列表,并生成一个三元树正则表达式。
添加或删除sku时,请重新生成正则表达式。可能是您的数据库
每周生成一次

此实用程序在不到半秒钟的时间内生成10000个字符串的正则表达式
大小并不重要,可能是300000个字符串

例如,这里是的regex


为什么不能直接使用
sku.Contains(sku)
?@IanKemp:一个充分的原因是它可能只是较长正则表达式的一部分。当备用分支以相同字符开头时,会导致回溯性能问题。因此,这个问题和方法似乎很有趣。@IanKemp-sku数组只是我的字符串源列表,用于生成一个正则表达式,我可以使用该正则表达式识别sku数组中的任何一个字符串。因此,在这段代码中,我想从
SKU
生成正则表达式,但在我的生产代码中,我从正则表达式中识别任何SKU。两段独立的代码。如果它都在一段代码中,我可以做
sku.Contains(sku)
,但事实并非如此。为什么不使用前缀树呢?只是想一想,如果您已经以某种方式将regex导入到生产环境中,也许您可以导入sku的数组。有趣的问题它不适用于任何长度的字符串吗?我刚刚测试了
var skus=new[]{“BATP03”、“BATTWP03”、“CBATTWLP04”},此代码生成了正确的
(BAT(P03 | TWP03)| CBATTWLP04)
regex。你能为将来的读者解释一下这段代码吗?试试
var skus=new[]{“BATP0”、“BATP03”、“BATTWP03”}。我所做的是按SKU的第一个字母对其进行分组,并递归地对字符串的其余部分进行分组。。。对不起,我的英语…这是一个很棒的答案。比我想到的要快得多。它确实忽略了生成每个可能分组的要求,这样我就可以选择最短的版本——有时不分组的版本更短。但除此之外,这是非常好的。我猜您在结尾还使用了
.FirstOrDefault()
生成(SKU).OrderBy(x=>x).OrderBy(x=>x.Length).FirstOrDefault()。对吗?此外,这种方法无法提取最长的匹配项(NFA正则表达式中的替换项不必匹配最长的替换项)。因此,对于
var skus=new[]{“BATP0”、“BATP03”、“BATTWP03”},这将失败当它生成时。另一个答案也不能处理这个输入。@WiktorStribiżew-Dziękuję。我很感谢你花时间调查此事。我想一想。大约10000个SKU中有5个属于这一类别。
generate(skus).OrderBy(x => x).OrderBy(x => x.Length);
BAT(PAG003|TW(LP0(3|4)|SP04))|SPIFATB01 BAT(PAG003|TWLP0(3|4)|TWSP04)|SPIFATB01 BA(TPAG003|TTW(LP0(3|4)|SP04))|SPIFATB01 BAT(PAG003|TW(LP(03|04)|SP04))|SPIFATB01 BAT(PAG003|TW(LP03|LP04|SP04))|SPIFATB01 BAT(PAG003|TWLP(03|04)|TWSP04)|SPIFATB01 BATPAG003|BATTW(LP0(3|4)|SP04)|SPIFATB01 BA(TPAG003|TT(WLP0(3|4)|WSP04))|SPIFATB01 BA(TPAG003|TTW(LP(03|04)|SP04))|SPIFATB01 BA(TPAG003|TTW(LP03|LP04|SP04))|SPIFATB01 BA(TPAG003|TTWLP0(3|4)|TTWSP04)|SPIFATB01 BAT(PAG003|TWL(P0(3|4))|TWSP04)|SPIFATB01 BAT(PAG003|TWL(P03|P04)|TWSP04)|SPIFATB01 BATPAG003|BATT(WLP0(3|4)|WSP04)|SPIFATB01 BATPAG003|BATTW(LP(03|04)|SP04)|SPIFATB01 BATPAG003|BATTW(LP03|LP04|SP04)|SPIFATB01 BA(TPAG003|TT(WLP(03|04)|WSP04))|SPIFATB01 BA(TPAG003|TTWLP(03|04)|TTWSP04)|SPIFATB01 BAT(PAG003|TWLP03|TWLP04|TWSP04)|SPIFATB01 BATPAG003|BATT(WLP(03|04)|WSP04)|SPIFATB01 BA(TPAG003|TT(WL(P0(3|4))|WSP04))|SPIFATB01 BA(TPAG003|TT(WL(P03|P04)|WSP04))|SPIFATB01 BA(TPAG003|TT(WLP03|WLP04|WSP04))|SPIFATB01 BA(TPAG003|TTWL(P0(3|4))|TTWSP04)|SPIFATB01 BA(TPAG003|TTWL(P03|P04)|TTWSP04)|SPIFATB01 BATPAG003|BATT(WL(P0(3|4))|WSP04)|SPIFATB01 BATPAG003|BATT(WL(P03|P04)|WSP04)|SPIFATB01 BATPAG003|BATT(WLP03|WLP04|WSP04)|SPIFATB01 BATPAG003|BATTWLP0(3|4)|BATTWSP04|SPIFATB01 BATPAG003|BATTWLP(03|04)|BATTWSP04|SPIFATB01 BA(TPAG003|TTWLP03|TTWLP04|TTWSP04)|SPIFATB01 BATPAG003|BATTWL(P0(3|4))|BATTWSP04|SPIFATB01 BATPAG003|BATTWL(P03|P04)|BATTWSP04|SPIFATB01