草庐IT

php - 根据括号递归地按逗号展开

coder 2024-04-30 原文

想象这样一个字符串:

field1,field2(subfield1),field3(subfield2,subfield3),field4(),field5(subfield4(subsubfield,subsubfield2))

我想要一个这样的数组:

array(
    field1 => array(),
    field2 => array(subfield1),
    field3 => array(
        subfield2,
        subfield3
    ),
    field4 => array(),
    field5 => array(
        subfield4 => array(
            subsubfield => array(),
            subsubfield => array()
        )
    )
)

我有这个正则表达式 [a-zA-Z0-9]*\([^()]*(?:(?R)[^()]*)*\) 其中做一些输出的工作:

array(
    field1,
    field2(subfield1),
    field3(subfield2,subfield3),
    field4(),
    field5(subfield4(subsubfield,subsubfield2))
)

虽然这不是我想要的。我现在有点卡住了,但到目前为止我想到的选项是:

  1. 用 preg_replace_callback 做点什么
  2. 为这些层次化的逗号分隔字符串编写某种自定义解析器
  3. 退出,​​喝醉,明天破案(好吧,没办法,现在就得这么做)

不管怎样,我必须遍历字段和子字段。 我有一些代码使用给定的正则表达式,并在稍后需要时对其值运行相同的匹配。我想一次解析整个字符串,包括它的嵌套子字符串。

有谁知道我是如何着手做这件事的?哪个选项是最好(或更好)的方法? (可读性 vs 资源使用 vs 复杂性 vs 等)

最佳答案

简介

你描述的问题不能用正则语言来表示,因为正则语言不能平衡括号。但是,多年来,大多数正则表达式实现都添加了一些功能,这些功能允许解析比常规语言更复杂的语言。特别是,这个问题可以通过 .NET 的平衡匹配或 PCRE's recursive expressions 来解决。 (感谢@Gumbo 在评论中指出这一点)。

但是,仅仅因为您可以做某事并不意味着您应该。将正则表达式用于此类任务的问题在于,随着您扩展元语言,修改正则表达式的难度将成倍增加。而解析器往往更具可塑性和易于扩展。

因此,您或许可以构建一系列正则表达式来涵盖输入的非病态情况,但既然可以编写解析器,为什么还要尝试呢?它们易于维护、速度极快(比正则表达式快)、易于扩展并且启动起来很有趣。

本来想念这道题是找PHP解法,所以就用JavaScript写了。我将其翻译成 PHP,并在帖子末尾留下了原始的 JavaScript 解决方案。

PHP 解决方案

function parse( $s ) {
    // we will always have a "current context".  the current context is the array we're
    // currently operating in.  when we start, this is simply an empty array.  as new
    // arrays are created, this context will change.
    $context = array();
    // since we have to keep track of how deep our context is, we keep a context stack
    $contextStack = array(&$context);
    // this accumulates the name of the current array
    $name = '';
    for( $i=0; $i<strlen($s); $i++ ) {
        switch( $s[$i] ) {
            case ',':
                // if the last array hasn't been added to the current context
                // (as will be the case for arrays lacking parens), we add it now
                if( $name!='' && !array_key_exists( $name, $context ) )
                    $context[$name] = array();
                // reset name accumulator
                $name = '';
                break;
            case '(':
                // we are entering a subcontext

                // save a new array in the current context; this will become our new context
                $context[$name] = array();
                // switch context and add to context stack
                $context = &$context[$name];
                $contextStack[] = &$context;
                // reset name accumulator
                $name = '';
                break;
            case ')':
                // we are exiting a context

                // if we haven't saved this array in the current context, do so now
                if( $name!='' && !array_key_exists( $name, $context ) )
                    $context[$name] = array();
                // we can't just assign $context the return value of array_pop because
                // that does not return a reference
                array_pop($contextStack);
                if( count($contextStack) == 0 ) throw new Exception( 'Unmatched parenthesis' );
                $context = &$contextStack[count($contextStack)-1];
                // reset name accumulator
                $name = '';
                break;
            default:
                // this is part of the field name
                $name .= $s[$i];
        }
    }
    // add any trailing arrays to the context (this will cover the case
    // where our input ends in an array without parents)
    if( $name!='' && !array_key_exists( $name, $context ) )
        $context[$name] = array();
    if( count( $contextStack ) != 1 ) throw new Exception( 'Unmatched parenthesis' );
    return array_pop( $contextStack );
}

原始 JavaScript 解决方案

function parse(s) {
    var root = { parent: null, children: [] };
    var field = { parent: root, name: '', start_idx: 0, children: [] };
    root.children.push( field );
    for( var i=0; i<s.length; i++ ) {
        switch( s[i] ) {
            case ',':
                // if this field didn't have any children, we have to set its text
                if( !field.children.length )
                    field.text = s.substr( field.start_idx, i - field.start_idx + 1 );
                // start a new field; create new field and change context
                var newfield = { parent: field.parent, name: '', start_idx: i, children:[] };
                field.parent.children.push(newfield);
                field = newfield;
                break;
            case '(':
                // start of a subfield; create subfield and change context
                var subfield = { parent: field, name: '', start_idx: i, children:[] };
                field.children.push(subfield);
                field = subfield;
                break;
            case ')':
                // end of a subfield; fill out subfield details and change context
                if( !field.parent ) throw new Error( 'Unmatched parenthesis!' );
                field.text = s.substr( field.start_idx, i - field.start_idx + 1 );
                if( field.text==='()' ) {
                    // empty subfield; pop this subfield so it doesn't clutter the parent
                    field.parent.children.pop();
                }
                field = field.parent;
                break;
            default:
                // this is part of the field name
                field.name += s[i];
                field.name = field.name.trim();
        }
    }
    return root;
}

现在我们有了您的语言的解析树,我们可以很容易地创建一些递归代码来发出您的 PHP:

function formatphp_namedarray(arr,indent,lastchild) {
    var php = indent + arr.name + ' => array(';
    if( arr.children.length ) {
        if( arr.children.length===1 && arr.children[0].length===0 ) {
            php += arr.children[0].name;
        } else {
            php += '\n';
            indent += '\t';
            for( var i=0; i<arr.children.length; i++ )
                php += formatphp_namedarray(arr.children[i],indent,i===arr.children.length-1);
            indent = indent.replace(/\t$/,'');
            php += indent;
        }
    }
    php += (lastchild?')':'),') + '\n';
    return php;
}

function formatphp(t) {
    var php = 'array(\n';
    for( var i=0; i<t.children.length; i++ )
        php += formatphp_namedarray( t.children[i], '\t', i===t.children.length-1 );
    php += ')'
    return php;
}       

在这里查看所有工作:http://jsfiddle.net/6bguY/1/

关于php - 根据括号递归地按逗号展开,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17507617/

有关php - 根据括号递归地按逗号展开的更多相关文章

  1. ruby - 用逗号、双引号和编码解析 csv - 2

    我正在使用ruby​​1.9解析以下带有MacRoman字符的csv文件#encoding:ISO-8859-1#csv_parse.csvName,main-dialogue"Marceu","Giveittohimóhe,hiswife."我做了以下解析。require'csv'input_string=File.read("../csv_parse.rb").force_encoding("ISO-8859-1").encode("UTF-8")#=>"Name,main-dialogue\r\n\"Marceu\",\"Giveittohim\x97he,hiswife.\"\

  2. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  3. ruby - 带括号和 splat 运算符的并行赋值 - 2

    我明白了:x,(y,z)=1,*[2,3]x#=>1y#=>2z#=>nil我想知道为什么z的值为nil。 最佳答案 x,(y,z)=1,*[2,3]右侧的splat*是内联扩展的,所以它等同于:x,(y,z)=1,2,3左边带括号的列表被视为嵌套赋值,所以它等价于:x=1y,z=23被丢弃,而z被分配给nil。 关于ruby-带括号和splat运算符的并行赋值,我们在StackOverflow上找到一个类似的问题: https://stackoverflow

  4. ruby - 如何使用 Selenium Webdriver 根据 div 的内容执行操作? - 2

    我有一个使用SeleniumWebdriver和Nokogiri的Ruby应用程序。我想选择一个类,然后对于那个类对应的每个div,我想根据div的内容执行一个Action。例如,我正在解析以下页面:https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=puppies这是一个搜索结果页面,我正在寻找描述中包含“Adoption”一词的第一个结果。因此机器人应该寻找带有className:"result"的div,对于每个检查它的.descriptiondiv是否包含单词“adoption

  5. ruby - 如何根据长度将路径数组转换为嵌套数组或散列 - 2

    我需要根据字符串路径的长度将字符串路径数组转换为符号、哈希和数组的数组给定以下数组:array=["info","services","about/company","about/history/part1","about/history/part2"]我想生成以下输出,对不同级别进行分组,根据级别的结构混合使用符号和对象。产生以下输出:[:info,:services,about:[:company,history:[:part1,:part2]]]#altsyntax[:info,:services,{:about=>[:company,{:history=>[:part1,:pa

  6. Ruby 正则表达式匹配逗号,但忽略括号中的逗号 - 2

    我正在尝试通过正则表达式拆分参数列表。这是一个带有我的参数列表的字符串:"a=b,c=3,d=[1,3,5,7],e,f=g"我想要的是:["a=b","c=3","d=[1,3,5,7]","e","f=g"]我试过先行,但Ruby不允许使用动态范围后行,所以这行不通:/(?如何让正则表达式忽略方括号中的所有内容? 最佳答案 也许这样的东西对你有用:str.scan(/(?:\[.*?\]|[^,])+/)编辑再三考虑。简单的非贪婪匹配器在某些嵌套括号的情况下会失败。 关于Ruby正则

  7. ruby - 如何在ruby中提取方括号内的内容 - 2

    我正在尝试提取方括号内的内容。到目前为止,我一直在使用它,它有效,但我想知道我是否可以直接在正则表达式中使用某些东西,而不是使用这个删除功能。a="Thisissuchagreatday[coolawesome]"a[/\[.*?\]/].delete('[]')#=>"coolawesome" 最佳答案 差不多。a="Thisissuchagreatday[coolawesome]"a[/\[(.*?)\]/,1]#=>"coolawesome"a[/(?"coolawesome"第一个依赖于提取组而不是完全匹配;第二个利用前瞻和

  8. ruby-on-rails - 在 fastercsv 中转义逗号值 - 2

    我正在研究csv生成。我正在分隔由逗号(,)分隔的值。如果字段中的值包含逗号,则不应在excel中分隔该字段。所以我想在那里放一个转义字符。我正在使用FasterCsv。那么我如何放置转义字符。fastercsv的转义字符是什么? 最佳答案 只需引用每个字段(默认为双引号),其中的逗号将被忽略:CSV.generate(:col_sep=>',',:quote_char=>'"')do|row|row"\"Quid,quid\",latinumdictum\n\"sit,altum\",viditur.\n"

  9. ruby - 递归地将所有数字字符串转换为 Ruby 哈希中的整数 - 2

    我有一个随机大小的散列,它可能有类似"100"的值,我想将其转换为整数。我知道我可以使用value.to_iifvalue.to_i.to_s==value来做到这一点,但我不确定我将如何在我的散列中递归地做到这一点,考虑到一个值可以是一个字符串,或一个数组(哈希或字符串),或另一个哈希。 最佳答案 这是一个非常简单的递归实现(尽管必须同时处理数组和散列会增加一些技巧)。deffixnumifyobjifobj.respond_to?:to_i#IfwecancastittoaFixnum,doit.obj.to_ielsifobj

  10. Ruby:标准递归模式 - 2

    我经常迷上ruby​​的一件事是递归模式。例如,假设我有一个数组,它可能包含无限深度的数组作为元素。所以,例如:my_array=[1,[2,3,[4,5,[6,7]]]]我想创建一个方法,可以将数组展平为[1,2,3,4,5,6,7]。我知道.flatten可以完成这项工作,但这个问题是作为我经常遇到的递归问题的一个例子-因此我试图找到一个更可重用的解决方案。简而言之-我猜这种事情有一个标准模式,但我想不出任何特别优雅的东西。任何想法表示赞赏 最佳答案 递归是一种方法,它不依赖于语言。您在编写算法时要考虑两种情况:再次调用函数的情

随机推荐