【问题标题】:PHP string console parameters to arrayPHP字符串控制台参数到数组
【发布时间】:2013-02-04 10:26:01
【问题描述】:

我想知道如何将给定的字符串转换为指定的数组:

字符串

all ("hi there \(option\)", (this, that), other) another

需要的结果(数组)

[0] => all,
[1] => Array(
    [0] => "hi there \(option\)",
    [1] => Array(
        [0] => this,
        [1] => that
    ),
    [2] => other
),
[2] => another

这用于我在 PHP 上制作的一种控制台。 我尝试使用preg_match_all,但是我不知道如何在括号内找到括号以“在数组中创建数组”。

编辑

示例中未指定的所有其他字符应视为String

编辑 2

我忘了提到括号外的所有参数都应该由space 字符检测。

【问题讨论】:

  • 您正在尝试构建语法树或解析树。我认为正则表达式不是一个合适的工具。
  • 那我该怎么办?
  • @CristianoSantos 编写你自己的解析器。
  • @CristianoSantos 您应该遍历输入字符串,这会将单词添加到数组中,直到访问右括号或输入完成。但是在访问开括号时,此方法必须调用自身(递归调用)并使用返回的数组。
  • 为什么不直接用[\s,()]+拆分

标签: php regex arrays preg-match explode


【解决方案1】:

10,000 英尺概览

您需要使用小型自定义解析器来执行此操作:代码获取此表单的输入并将其转换为您想要的表单。

在实践中,我发现根据复杂性将此类解析问题归为以下三个类别之一很有用:

  1. 琐碎:可以通过几个循环和人性化的正则表达式解决的问题。这个类别很诱人:如果您甚至有点不确定问题是否可以通过这种方式解决,一个好的经验法则是确定它不能。
  2. 简单: 需要自己构建一个小型解析器的问题,但仍然足够简单,因此拿出大炮没有什么意义。如果您需要编写超过 100 行代码,请考虑升级到下一个类别。
  3. 涉及的问题:正式化并使用已经存在的、经过验证的解析器生成器¹是有意义的问题。

我将此特定问题归为第二类,这意味着您可以这样处理它:

编写一个小型解析器

定义语法

为此,您必须首先定义——至少是非正式的,并附上一些简短的注释——你要解析的语法。请记住,大多数语法都是在某些时候递归定义的。所以假设我们的语法是:

  • 输入是一个序列
  • 序列是一系列零个或多个标记
  • 标记可以是单词字符串数组
  • 标记由一个或多个空格字符分隔
  • 单词是一系列字母字符 (a-z)
  • 字符串是用双引号括起来的任意字符序列
  • 数组是由逗号分隔的一系列一个或多个标记

你可以看到我们在一个地方有递归:一个序列可以包含数组,一个数组也是根据一个序列定义的(所以它可以包含更多的数组等)。

像上面那样非正式地处理这个问题作为介绍更容易,但是如果你这样做formally,对语法的推理会更容易。

构建词法分析器

掌握了语法,您知道需要将输入分解为标记,以便对其进行处理。接受用户输入并将其转换为语法定义的各个部分的组件称为 lexer。词法分析器是愚蠢的;他们只关心输入的“外观”,并不试图检查它是否真的有意义。

这是我为解析上述语法而编写的一个简单的词法分析器(不要将它用于任何重要的事情;可能包含错误):

$input = 'all ("hi there", (this, that) , other) another';

$tokens = array();
$input = trim($input);
while($input) {
    switch (substr($input, 0, 1)) {
        case '"':
            if (!preg_match('/^"([^"]*)"(.*)$/', $input, $matches)) {
                die; // TODO: error: unterminated string
            }

            $tokens[] = array('string', $matches[1]);
            $input = $matches[2];
            break;
        case '(':
            $tokens[] = array('open', null);
            $input = substr($input, 1);
            break;
        case ')':
            $tokens[] = array('close', null);
            $input = substr($input, 1);
            break;
        case ',':
            $tokens[] = array('comma', null);
            $input = substr($input, 1);
            break;
        default:
            list($word, $input) = array_pad(
                preg_split('/(?=[^a-zA-Z])/', $input, 2),
                2,
                null);
            $tokens[] = array('word', $word);
            break;
    }
    $input = trim($input);
}

print_r($tokens);

构建解析器

完成此操作后,下一步是构建一个 parser:一个检查词法输入并将其转换为所需格式的组件。解析器很聪明;在转换输入的过程中,它还确保输入符合语法规则。

解析器通常被实现为state machines(也称为有限状态机或有限自动机),其工作方式如下:

  • 解析器有一个状态;这通常是一个适当范围内的数字,但每个状态也用更人性化的名称来描述。
  • 有一个循环,每次读取一个读取 lexed 标记。根据当前状态和令牌的值,解析器可以决定执行以下一项或多项操作:
    1. 采取一些影响其输出的操作
    2. 将其状态更改为其他值
    3. 判断输入格式错误并产生错误

¹ 解析器生成器是一种程序,其输入是形式语法,输出是词法分析器和解析器,您可以“加水”到:只需扩展代码以根据标记的类型执行“采取一些行动”;其他一切都已经处理好了。快速搜索这个主题给出了 led PHP Lexer and Parser Generator?

【讨论】:

  • 尊重@Jon。你能链接任何关于这个主题的好文章吗?
  • 不需要定义语言,因为扩展的正则表达式可以解决这个问题。我打赌一种表达。我认为如果服务员不知道正式语言,这个答案对他/她来说更加误导。 P.S:哇,这个解决方案对于 PHP 应用程序来说变得更加复杂,我们不是在这里构建一种语言。 :D
  • @Dyin:这取决于你如何定义“需要”。如果您想要一个可维护的解析器,那么您绝对需要一种语法。如果您想要一个适合我 (tm) 但完全无法理解的正则表达式,可能会因最轻微的挑衅而崩溃,并且最终无法在未来扩展,那么您不一定需要语法。如果您不同意,请尝试通过编写这样的正则表达式来证明我错了。
  • 正则表达式怎么无法扩展?真正的正则表达式无法告诉您语法错误在哪里,但答案不应该为此实现 LALR 或 SLR。 :D 遗憾的是,我不是扩展递归正则表达式方面的专家,但我相信,有人会为此实现一个模式,它可以一步解决问题,即更快。这是一个括号问题,为什么PHP开发人员要为此编写词法和句法分析器?
  • @Dyin:我也不是正则表达式方面的专家,但我知道我永远不会想用正则表达式来做这件事,因为 a) 我认为不可能 证明一个正则表达式在所有情况下都能正常工作(当然可以证明一个解析器正确处理了一个给定的形式语法)和b)更容易推理一个FSM 有效,因此更容易扩展和维护。 YMMV。
【解决方案2】:

毫无疑问,如果您正在构建语法树,您应该编写解析器。但如果您只需要解析 this 示例输入 regex 仍然可能是一个工具:

<?php
$str = 'all, ("hi there", (these, that) , other), another';

$str = preg_replace('/\, /', ',', $str); //get rid off extra spaces
/*
 * get rid off undefined constants with surrounding them with quotes
*/
$str = preg_replace('/(\w+),/', '\'$1\',', $str);
$str = preg_replace('/(\w+)\)/', '\'$1\')', $str);
$str = preg_replace('/,(\w+)/', ',\'$1\'', $str);

$str = str_replace('(', 'array(', $str);

$str = 'array('.$str.');';

echo '<pre>';
eval('$res = '.$str); //eval is evil.
print_r($res); //print the result

Demo.

注意:如果输入格式错误,正则表达式肯定会失败。我正在编写此解决方案,以防您需要快速脚本。编写词法分析器和解析器是一项耗时的工作,需要大量研究。

【讨论】:

  • 谢谢,我真的只是需要它来快速工作。就我而言,如果正则表达式失败,则根本没有问题。我真的只需要抛出一个一般错误而不是一个特定的错误。 =)
  • @CristianoSantos 在这种情况下,我会使用这个脚本并开始阅读更多关于语法解析器的教育目的。
  • 这个字符串太乱了'all, ("hi, there, I am from SO", (these, that) , other), another'
  • @nhahtdh 是的,因为逗号。我已经注意到正则表达式是一种工具,在这种情况下,字符串将按照它们在示例中的形式形成。
  • @CristianoSantos 哦,当我写这个答案时,没有特殊字符有问题。如果您需要转义它们并改进此脚本以更好地处理格式错误的字符串。对于工作没关系,对于未来的使用目的很大。
【解决方案3】:

据我所知,括号问题是乔姆斯基语言class 2,而正则表达式相当于乔姆斯基语言class 3,所以应该没有正则表达式,这样就解决了这个问题。

但不久前我读到了一些东西:

此 PCRE 模式解决了括号问题(假设设置了 PCRE_EXTENDED 选项以忽略空格):\( ( (?&gt;[^()]+) | (?R) )* \)

带分隔符且不带空格:/\(((?&gt;[^()]+)|(?R))*\)/

这是来自Recursive Patterns (PCRE) - PHP manual

该手册上有一个示例,它解决了您指定的几乎相同的问题! 您或其他人可能会找到它并继续这个想法。

我认为最好的解决方案是用preg_match_all 编写一个病态的递归模式。可悲的是,我没有能力做这种疯狂的事!

【讨论】:

  • 你在现代语言中看到的正则表达式并不是严格正则的,所以它可以做超出理论正则表达式所能做的事情。
【解决方案4】:

首先,我要感谢所有帮助过我的人。

很遗憾,我不能接受多个答案,因为如果可以的话,我会给你们所有人,因为对于不同类型的问题,所有答案都是正确的。

就我而言,我只需要一些简单而肮脏的东西,并且按照@palindrom 和@PLB 的回答,我有以下工作适合我:

$str=transformEnd(transformStart($string));
$str = preg_replace('/([^\\\])\(/', '$1array(', $str);
$str = 'array('.$str.');';
eval('$res = '.$str);
print_r($res); //print the result

function transformStart($str){
    $match=preg_match('/(^\(|[^\\\]\()/', $str, $positions, PREG_OFFSET_CAPTURE);
    if (count($positions[0]))
        $first=($positions[0][1]+1);
    if ($first>1){
        $start=substr($str, 0,$first);
        preg_match_all("/(?:(?:\"(?:\\\\\"|[^\"])+\")|(?:'(?:\\\'|[^'])+')|(?:(?:[^\s^\,^\"^\']+)))/is",$start,$results);
        if (count($results[0])){
            $start=implode(",", $results[0]).",";
        } else {
            $start="";
        }
        $temp=substr($str, $first);
        $str=$start.$temp;
    }
    return $str;
}

function transformEnd($str){
    $match=preg_match('/(^\)|[^\\\]\))/', $str, $positions, PREG_OFFSET_CAPTURE);
    if (($total=count($positions)) && count($positions[$total-1]))
        $last=($positions[$total-1][1]+1);
    if ($last==null)
        $last=-1;
    if ($last<strlen($str)-1){
        $end=substr($str,$last+1);
        preg_match_all("/(?:(?:\"(?:\\\\\"|[^\"])+\")|(?:'(?:\\\'|[^'])+')|(?:(?:[^\s^\,^\"^\']+)))/is",$end,$results);
        if (count($results[0])){
            $end=",".implode(",", $results[0]);
        } else {
            $end="";
        }
        $temp=substr($str, 0,$last+1);
        $str=$temp.$end;
    }
    if ($last==-1){
        $str=substr($str, 1);
    }
    return $str;
}

其他答案对于正在寻找更好方法的人也很有帮助。

再次感谢大家=D。

【讨论】:

    【解决方案5】:

    我会放上实现这个的算法或伪代码。希望你能弄清楚如何在 PHP 中实现它:

    function Parser([receives] input:string) returns Array
    
    define Array returnValue;
    
    for each integer i from 0 to length of input string do
        charachter = ith character from input string.
    
        if character is '('
            returnValue.Add(Parser(substring of input after i)); // recursive call
    
        else if character is '"'
            returnValue.Add(substring of input from i to the next '"')
    
        else if character is whitespace
            continue
    
        else
            returnValue.Add(substring of input from i to the next space or end of input)
    
       increment i to the index actually consumed
    
    
    return returnValue
    

    【讨论】:

    • 那么,我真的需要逐字符解析字符串吗?
    • 嗯,你可以先提取单词。但是你应该对报价保持谨慎。我建议逐字符进行。
    【解决方案6】:

    我想知道这是否有效:

    1. ( 替换为Array(
    2. 使用正则表达式将逗号放在不带逗号的单词或括号之后

      preg_replace( '/[^,]\s+/', ',', $string )

    3. eval( "\$result = Array( $string )" )

    【讨论】:

    • 如果你评估 thesethat 你会得到未定义的常量错误。
    • @h2ooooooo:结果数组也有这些常量。所以我猜它们一开始就不是常数。
    • 但我的字符串以“all”开头。在eval 上不会报错吗?
    • 这将在all ("hi there", (this, that) , other) another 中触发错误,因为)another 之间缺少,...
    • @Jeffrey 正则表达式也可以解决这个问题,我猜。
    【解决方案7】:

    如果字符串值是固定的,可以像这样完成

    $ar = explode('("', $st);
    
    $ar[1] = explode('",', $ar[1]);
    
    $ar[1][1] = explode(',', $ar[1][1]);
    
    $ar[1][2] = explode(')',$ar[1][1][2]);
    
    unset($ar[1][1][2]);
    
    $ar[2] =$ar[1][2][1];
    
    unset($ar[1][2][1]);
    

    【讨论】:

    • 抱歉,它们是动态的 =S
    猜你喜欢
    • 1970-01-01
    • 2015-02-17
    • 2018-12-21
    • 1970-01-01
    • 2016-01-09
    • 2012-06-12
    • 1970-01-01
    • 2011-11-21
    • 1970-01-01
    相关资源
    最近更新 更多