我想知道如何将给定的字符串转换为指定的数组:
字符串
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
字符检测。
10000英尺概览
您需要使用一个小型自定义解析器来完成此操作:代码接受此表单的输入并将其转换为您想要的表单。
在实践中,我发现将这样的解析问题根据其复杂性分为三类很有用:
- 琐碎:只需几个循环和人性化的正则表达式就可以解决的问题。这一类很有诱惑力:如果你甚至有点不确定问题是否可以通过这种方式解决,一个好的经验法则是决定它不能
- 简单:需要自己构建一个小型解析器的问题,但仍然足够简单,因此提出大型解析器是没有意义的。如果您需要编写大约100行以上的代码,那么可以考虑升级到下一个类别
- 涉及的问题:对于这些问题,正式化并使用已经存在的、经过验证的解析器生成器是有意义的
我把这个特殊的问题归类为第二类,这意味着你可以这样处理它:
编写小型解析器
定义语法
要做到这一点,您必须首先定义要解析的语法,至少是非正式地,并附上一些快速注释。请记住,大多数语法都是在某个时刻递归定义的。假设我们的语法是:
- 输入是序列
- 序列是一系列零个或多个标记
- 令牌是单词、字符串或数组
- 令牌由一个或多个空白字符分隔
- 单词是一个字母字符序列(A-z)
- 字符串是用双引号括起来的任意字符序列
- 数组是一系列由逗号分隔的一个或多个标记
你可以看到,我们在一个地方有递归:序列可以包含数组,数组也可以根据序列来定义(所以它可以包含更多的数组等)。
如上所述非正式地处理这件事作为引言更容易,但如果你正式地这样做,关于语法的推理会更容易。
构建lexer
掌握语法后,您知道需要将输入分解为标记,以便对其进行处理。接受用户输入并将其转换为语法定义的各个部分的组件称为lexer。骗子是愚蠢的;他们只关心输入的"外观",而不试图检查它是否真的有意义。
以下是我为解析上述语法而编写的一个简单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);
构建解析器
完成此操作后,下一步是构建解析器:一个检查lexed输入并将其转换为所需格式的组件。解析器是聪明的;在转换输入的过程中,它还确保输入是由语法规则形成的。
解析器通常被实现为状态机(也称为有限状态机或有限自动机),其工作方式如下:
- 解析器具有状态;这通常是一个适当范围内的数字,但每个州也用一个更人性化的名称来描述
- 有一个循环,每次读取一个lexed令牌。基于令牌的当前状态和值,解析器可以决定执行以下一项或多项操作:
- 采取一些影响其输出的操作
- 将其状态更改为其他值
- 判定输入格式不正确并产生错误
1.3.3解析器生成器是一种程序,其输入是一种形式语法,其输出是一个lexer和一个解析器,你可以"加水":只需扩展代码,根据令牌的类型执行"采取一些行动";其他一切都已经处理好了。关于这个主题的快速搜索给出了led PHP Lexer和Parser生成器?
如果您正在构建语法树,那么毫无疑问,您应该编写解析器。但是,如果您只需要解析,这个示例输入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
演示。
注意:如果输入格式不正确,regex肯定会失败。我写这个解决方案只是为了以防万一你需要快速脚本。编写lexer和解析器是一项耗时的工作,需要大量的研究。
据我所知,括号问题是一个乔姆斯基语言class 2,而正则表达式等价于乔姆斯基语class3。因此应该没有正则表达式,这就解决了这个问题。
但不久前我读到一篇文章:
这个PCRE模式解决了括号问题(假设PCRE_EXTENDED选项被设置为忽略空白):
'( ( (?>[^()]+) | (?R) )* ')
带分隔符但不带空格:/'(((?>[^()]+)|(?R))*')/
。
这是从递归模式(PCRE)-PHP手册。
手册上有一个例子,它几乎解决了与您指定的问题相同的问题!你或其他人可能会找到它并继续这个想法。
我认为最好的解决方案是用preg_match_all
编写一个病态的递归模式。遗憾的是,我没有能力做这种疯狂的事!
首先,我要感谢在这方面帮助我的每一个人。
不幸的是,我不能接受多个答案,因为如果可以的话,我会给你们所有的答案,因为对于不同类型的问题,所有的答案都是正确的。
在我的情况下,我只需要一些简单而肮脏的东西,按照@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。
我将放入算法或伪代码来实现这一点。希望你能弄清楚如何在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
我想知道这是否有效:
- 用
Array(
替换(
使用正则表达式将逗号放在没有逗号的单词或括号后面
preg_replace( '/[^,]'s+/', ',', $string )
eval( "'$result = Array( $string )" )
如果字符串值是固定的,可以像这个一样进行一些操作
$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]);