You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将字符串列表编译为紧凑的正则表达式?

问题描述

先举几个例子说明需求:

基础场景

  • 输入字符串:'Example', 'DOMNode', 'DOMText', 'DOMElement'
  • 期望输出:(Example|DOM(Node|Text|Element))

进阶场景(两端匹配公共部分)

  • 输入字符串:'Example', 'ArrayIterator', 'RecursiveArrayIterator', 'DirectoryIterator', 'RecursiveDirectoryIterator'
  • 期望输出:(Example|(Recursive)?(Array|Directory)Iterator)

我有一组需要匹配的目标字符串,目前是直接用交替符|拼接成正则表达式,但希望能提取公共前缀/后缀,合并成更简洁的分组结构。这只是练手项目,但卡壳好久了。

我试过拆分字符、匹配各种组合,熬了好几天夜,记不清所有尝试过的方法了,就是找不到能提取公共部分并重建正则的正确思路。


已实现的下划线分隔字符串处理函数

我有个处理下划线分隔字符串的函数,按下划线拆分前缀时效果不错:

<?php
/**
 * separates snake case names into nested hierarchies.
 */
function processArray(array $funcs): array
 {
    $loop = false;
    $current = false;
    $newFuncs = [];
    foreach ($funcs as $name)
     {
        $pos = strpos($name, '_');
        if ($current and !str_starts_with($name, $current))
         {
            if ($loop || $pos)
             {
                $newFuncs[$current] = processArray($newFuncs[$current]);
                $loop = false;
             }
            $current = false;
         }
        if ($pos)
         {
            $current = substr($name, 0, $pos + 1);
            $newFuncs[$current] ??= [];
            $subName = substr($name, $pos + 1);
            $newFuncs[$current][] = $subName;
            if (strpos($subName, '_'))
             {
                $loop = true;
             }
         }
        else
         {
            if ($loop)
             {
                $newFuncs[$current] = processArray($newFuncs[$current]);
                $loop = false;
             }
            $current = false;
            $newFuncs[] = $name;
         }
     }
    return $newFuncs;
 }

function getRegex(array $strs): string
 {
    static $level = 0;
    $ret = '(';
    foreach ($strs as $key => $value)
     {
        if (is_array($value))
         {
            $strs[$key] = (is_string($key)?$key:'').getRegex($value);
         }
     }
    $ret.= implode('|', $strs);
    $ret.= ')';
    return $ret;
 }


$funcs = get_defined_functions()['internal'];
sort($funcs);

$funcs = processArray($funcs);
$getRegex = getRegex($funcs);

//remove isolated groups (groups with only one alternation)
do
 {
    $getRegex = preg_replace('~\(([a-zA-Z_0-9]+?)\)~', '$1', $getRegex, -1, $count);
 } 
while ($count);

var_dump($getRegex);

更新:新问题与当前实现

我已经接近解决问题,但遇到了新情况:当存在可选部分时,会在可选字符串前生成空交替项,导致正则优先匹配空值,无法正确匹配带可选内容的字符串。比如生成的My(|Opt)无法匹配MyOpt。

以下是当前的实现代码:

function processRegexArray(array $funcs): array
 {
    $loop = false;
    $current = false;
    $newFuncs = [];
    foreach ($funcs as $name)
     {
        preg_match('~^([A-Z]+(?=[A-Z])|[A-Z0-9_]*[a-z]+)~', $name, $matches);
        $pos = strlen($matches[0]??='') -1;
        if ($current and !str_starts_with($name, $current))
         {
            if ($loop || $pos)
             {
                $newFuncs[$current] = processRegexArray($newFuncs[$current]);
                $loop = false;
             }
            $current = false;
         }
        if ($pos)
         {
            $current = substr($name, 0, $pos + 1);
            $newFuncs[$current] ??= [];
            $subName = substr($name, $pos + 1);
            $newFuncs[$current][] = $subName;
            preg_match('~^([A-Z]+(?=[A-Z])|[A-Z0-9_]*[a-z]+)~', $subName, $matches);
            if ($matches)
             {
                $loop = true;
             }
         }
        else
         {
            if ($loop)
             {
                $newFuncs[$current] = processRegexArray($newFuncs[$current]);
                $loop = false;
             }
            $current = false;
            $newFuncs[] = $name;
         }
     }
    return $newFuncs;
 }

function getRegex(array $strs): string
 {
    if (count($strs) === 0)
        return '';
    static $level = 0;
    $ret = '(';
    foreach ($strs as $key => $value)
     {
        if (is_array($value))
         {
            $strs[$key] = (is_string($key)?$key:'').getRegex($value);
         }
     }
    $ret.= implode('|', $strs);
    $ret.= ')';
    return $ret;
 }

$members = [...get_declared_classes(), ...get_declared_interfaces(), ...get_declared_traits()];
sort($members);

// remove namespaced names for now
foreach ($members as $key => $value)
 {
    if (strpos($value, '\\') !== false)
     {
        unset($members[$key]);
     }
 }
$members = processRegexArray($members);

$getRegex = getRegex($members);
do
 {
    $getRegex = preg_replace('~\(([^|()]*?)\)~', '$1', $getRegex, -1, $count);
 } 
while ($count);
var_dump($getRegex);

预期输出

string(2302) "(AllowDynamicProperties|AppendIterator|ArgumentCountError|ArithmeticError|Array(Access|Iterator|Object)|AssertionError|Attribute|BackedEnum|Bad(FunctionCallException|MethodCallException)|CURL(File|StringFile)|CachingIterator|CallbackFilterIterator|ClosedGeneratorException|Closure|CompileError|Countable|Curl(Handle|MultiHandle|ShareHandle)|DOM(Attr|CdataSection|CharacterData|ChildNode|Comment|Document|DocumentFragment|DocumentType|Element|Entity|EntityReference|Exception|Implementation|NameSpaceNode|NamedNodeMap|Node|NodeList|Notation|ParentNode|ProcessingInstruction|Text)|DOMXPath|Date(Interval|Period|Time(|Immutable|Interface|Zone))|DeflateContext|Directory(|Iterator)|DivisionByZeroError|DomainException|EmptyIterator|Error(|Exception)|Exception|Fiber(|Error)|FilesystemIterator|FilterIterator|Generator|GlobIterator|HashContext|InfiniteIterator|InflateContext|InternalIterator|InvalidArgumentException|Iterator(|Aggregate|Iterator)|Json(Exception|Serializable)|LengthException|LibXMLError|LimitIterator|LogicException|MultipleIterator|NoRewindIterator|Open(SSL(AsymmetricKey|Certificate|CertificateSigningRequest))|Out(OfBoundsException|OfRangeException)|OuterIterator|OverflowException|PDO|PDO(Exception|Row|Statement)|ParentIterator|ParseError|Phar(|Data|Exception|FileInfo)|PhpToken|RangeException|Rar(Archive|Entry|Exception)|Recursive(ArrayIterator|CachingIterator|CallbackFilterIterator|DirectoryIterator|FilterIterator|Iterator(|Iterator)|RegexIterator|TreeIterator)|Reflection(|Attribute|Class(|Constant)|Enum(|BackedCase|UnitCase)|Exception|Extension|Fiber|Function(|Abstract)|Generator|IntersectionType|Method|NamedType|Object|Parameter|Property|Reference|Type|UnionType|ZendExtension)|Reflector|RegexIterator|ReturnTypeWillChange|RuntimeException|SeekableIterator|Sensitive(Parameter(|Value))|Serializable|Session(Handler(|Interface)|IdInterface|UpdateTimestampHandlerInterface)|Simple(XML(Element|Iterator))|Spl(DoublyLinkedList|File(Info|Object)|FixedArray|Heap|MaxHeap|MinHeap|ObjectStorage|Observer|PriorityQueue|Queue|Stack|Subject|TempFileObject)|Stringable|Throwable|Traversable|TypeError|UnderflowException|UnexpectedValueException|UnhandledMatchError|UnitEnum|ValueError|Weak(Map|Reference)|XML(Parser|Reader|Writer)|__PHP_Incomplete_Class|finfo|php_user_filter|stdClass)"

内容的提问来源于stack exchange,提问作者yarns

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 23:27:23