将格式化的字符串解析为数组的数组

3
+2-1+18*+7-21+3*-4-5+6x29

上述字符串是我尝试分割成键值数组或类似结构的字符串示例。该字符串用于表示内部网站三列页面上各种类的布局,用户可以通过拖放进行编辑。此字符串存储在cookie中以便在下次访问时使用。
数字代表类的ID,-、+和x代表类的状态(最小化、展开或隐藏),*代表列断点。
我可以使用explode轻松将其分为三列,得到一个带有3个$key => $value关联的数组。
例如:
$column_layout = array( [0] => '+2-1+18' , [1] => '+7-21+3' , [2] => '-4-5+6x29' )

我需要将这个内容分成不同的类别,并保持状态和ID在一起。由于不同的类别和状态会因用户而异,每列中有多少个也会不同,因此我需要能够自动完成所有这些任务。
$column1 = array(
    array( '+' , 2 ),
    array( '-' , 1 ),
    array( '+' , 18 )
);
$column2 = array(...

抱歉,为了澄清,我需要能够自动完成这个操作。 我应该多提供一些关于如何使用它的信息,现在我已经补充完整了。 - andyface
1
澄清一下:$column1 = array('+' => 2, '-' => 1, '+' => 18) 是不可能的结果,因为在同一个数组/子数组中两个键不能相同。 - mickmackusa
我看到你的示例字符串有3个部分,由两个星号分隔。这总是有3个部分吗?每个部分总是有3组值吗? - mickmackusa
这是我重新阅读了10年后注意到的第一件事,很高兴知道我在这些年里至少有些进步。老实说,我现在已经记不起这是做什么的了,我想当时它可能只限于三个部分,但处理每个部分内的任意数量的值可能是有意义的。 - andyface
说实话,这可能是一个过于具体的问题,在SO上提出来标题也有点误导人,但没关系,当时我对整个事情还比较新。 - andyface
2个回答

7

首先,使用分隔符*对数组进行explode()操作。

然后,您可以使用preg_match_all匹配分割后数组中的每个项。以下是使用您提供的示例输入的代码示例。

$layout = explode('*', $input);
$columns = array();
foreach ( $layout as $item ){
    $parts = array();

    //matches either a -, x or + followed by one or more digits
    preg_match_all('/([+-x])(\d+)/', $item, $matches, PREG_SET_ORDER);

    foreach ( $matches as $match){ 
        //match[1] hold the + or -, match[2] holds the digits
        $parts[] = array($match[1], $match[2]);
    }
    $columns[] = $parts;
}

你的示例代码的输出结果如下所示:
array(
     array( array('+', '2'), array('-', '1'), array('+', '18') ),
     array( array('+', '7'), array('-', '21'), array('+', '3') ),
     //etc
);

使用PHP 5.3,您可以尝试以下代码(未经测试)。主要区别是内部循环已被替换为array_map,这样就不需要很多代码行了。(Array map将函数应用于数组中的每个项,并返回转换后的数组)。漂亮的闭包语法需要PHP 5.3支持。

$layout = explode('*', $input);
$columns = array();
foreach ( $layout as $item ){
    preg_match_all('/([+-x])(\d+)/', $item, $matches, PREG_SET_ORDER);
    $columns[] = array_map( function($a){ return array($a[1], $a[2]); },
                            $matches);
}

您可以完全删除循环:
$innerMatch = function($item){
    preg_match_all('/([+-x])(\d+)/', $item, $matches, PREG_SET_ORDER);
    return array_map( function($a){ return array($a[1], $a[2]); },
                      $matches);
};
$columns = array_map($innerMatch, explode('*', $input));

然而,这种方法的缺点是大多数PHP开发者都无法读懂它,因此我不建议使用它。


更多解释

@Christopher Altman的要求

PHP 5.3版本中唯一新增的部分就是这个:

array_map(
          function($a){ return array($a[1], $a[2]); },
          $matches
);

将其扩展和修改一些(以示例为例)

//bind an anonymous function to the variable $func
$func = function($a){
    return $a*$a; 
}; 
//$func() now calls the anonymous function we have just defined

//then we can call it like so:
$result = array_map($func, $myArray);

如果$myArray被定义为:

array(1,2,3,4);

当使用数组映射函数时,你可以将其视为转换为。
array(func(1),func(2),func(3),func(4));

但是由于PHP不是一种惰性求值的语言,所有函数都会在遇到时立即被计算,因此数组会作为array_map的返回值被返回:

array(2, 4, 9, 16)

在实际代码中,preg_match_all返回一组匹配结果(其中每个匹配结果都是一个数组)。因此,我所做的就是取出该数组,并对每个匹配结果应用一个函数,将其转换为所需格式的不同数组。

你能解释一下 PHP 5.3 的例子是如何工作的吗?它看起来很强大,只想快速了解每个部分的作用。 - Christopher Altman
非常好,谢谢。这正是我所需要的。 我尝试使用 preg_split,但它会去掉分隔符,这使得事情变得复杂。 - andyface
@Christopher 我添加了更多的解释。 - Yacoby
1
@andyface preg_split()有一个标志,可以保留分隔符。 - mickmackusa

0

假设您的严格格式化输入具有静态数量的段和每个段的值,则使用 sscanf() 作为解析字符串的(冗长的)直接方法而不是 preg_ 技术有一些优势。

  1. 这是一种直接的单函数技术。无需分解再解析。
  2. 此函数不会像 preg_match() 一样产生无用的“全字符串匹配”。
  3. 您不需要从 $matches 数组中挑选所需内容(与 preg_match() 相同)
  4. 数值已经被强制转换为整数(如果对您有用)。

代码:(演示

$layout = '+2-1+18*+7-21+3*-4-5+6x29';

sscanf(
    $layout,
    '%[-+x]%d%[-+x]%d%[-+x]%d*%[-+x]%d%[-+x]%d%[-+x]%d*%[-+x]%d%[-+x]%d%[-+x]%d',
    $column1[0][0], $column1[0][1], $column1[1][0], $column1[1][1], $column1[2][0], $column1[2][1],
    $column2[0][0], $column2[0][1], $column2[1][0], $column2[1][1], $column2[2][0], $column2[2][1],
    $column3[0][0], $column3[0][1], $column3[1][0], $column3[1][1], $column3[2][0], $column3[2][1]
);

var_export($column1);
echo "\n---\n";
var_export($column2);
echo "\n---\n";
var_export($column3);

输出:

array (
  0 => 
  array (
    0 => '+',
    1 => 2,
  ),
  1 => 
  array (
    0 => '-',
    1 => 1,
  ),
  2 => 
  array (
    0 => '+',
    1 => 18,
  ),
)
---
array (
  0 => 
  array (
    0 => '+',
    1 => 7,
  ),
  1 => 
  array (
    0 => '-',
    1 => 21,
  ),
  2 => 
  array (
    0 => '+',
    1 => 3,
  ),
)
---
array (
  0 => 
  array (
    0 => '-',
    1 => 4,
  ),
  1 => 
  array (
    0 => '-',
    1 => 5,
  ),
  2 => 
  array (
    0 => '+',
    1 => 6,
  ),
)

p.s.

  • 如果你想要结果是一个包含3个一级元素的单一数组,每个元素都包含3对符号-数字子数组,这也可以通过修改sscanf()中的引用变量来实现。
  • 如果你不喜欢格式字符串中的重复部分,你可以将重复的子模式声明为一个变量,并以编程方式重复它(当然要用星号分隔)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接