PHP:将一串交替的字符组拆分成一个数组
PHP: split a string of alternating groups of characters into an array
我有一个字符串,其正确语法是正则表达式 ^([0-9]+[abc])+$
。因此,有效字符串的示例为:'1a2b' 或 '00333b1119a555a0c'
为清楚起见,字符串是(值、字母)对的列表,顺序很重要。我坚持使用输入字符串,所以我无法更改它。虽然使用上述正则表达式测试正确的语法在原则上似乎很容易,但我试图在 PHP 中想出最有效的方法来将兼容的字符串转换为可用的数组,如下所示:
输入:
'00333b1119a555a0c'
输出:
array (
0 => array('num' => '00333', 'let' => 'b'),
1 => array('num' => '1119', 'let' => 'a'),
2 => array('num' => '555', 'let' => 'a'),
3 => array('num' => '0', 'let' => 'c')
)
我在使用 preg_match 时遇到困难。例如,这没有给出预期的结果,其目的是在 EITHER \d+(并保存)或 [abc](并保存)上进行贪婪匹配,重复直到到达字符串末尾。
$text = '00b000b0b';
$out = array();
$x = preg_match("/^(?:(\d+|[abc]))+$/", $text, $out);
这也不起作用,这里的目的是在 \d+[abc] 上进行贪婪匹配(并保存这些),重复直到到达字符串末尾,然后将它们拆分为数字和字母。
$text = '00b000b0b';
$out = array();
$x = preg_match("/^(?:\d+[abc])+$/", $text, $out);
我计划检查语法作为 preg_match 的一部分,然后使用 preg_match 输出贪婪匹配 'blocks'(或者如果使用 [= 则保留分隔符38=]),然后如果需要,使用 for (...; i+=2)
一次循环遍历结果 2 项以提取成对的值字母。
但我似乎连基本的 preg_split() 或 preg_match() 方法都无法顺利工作,更不用说探索是否有 'neater' 或更有效的方法了方式。
您的正则表达式需要一些匹配组
/([0-9]+?)([a-z])/i
这意味着匹配一组中的所有数字,匹配另一组中的所有字母。 Preg match all 获取所有匹配项。
正则表达式的关键是非贪婪标志 ?
,它匹配尽可能短的字符串。
match[0]
是整场比赛
match[1]
是第一个匹配组(数字)
match[2]
是第二个匹配组(字母)
下面的示例
<?php
$input = '00333b1119a555a0c';
$regex = '/([0-9]+?)([a-z])/i';
$out = [];
$parsed = [];
if (preg_match_all($regex, $input, $out)) {
foreach ($out[0] as $index => $value) {
$parsed[] = [
'num' => $out[1][$index],
'let' => $out[2][$index],
];
}
}
var_dump($parsed);
输出
array(4) {
[0] =>
array(2) {
'num' =>
string(5) "00333"
'let' =>
string(1) "b"
}
[1] =>
array(2) {
'num' =>
string(4) "1119"
'let' =>
string(1) "a"
}
[2] =>
array(2) {
'num' =>
string(3) "555"
'let' =>
string(1) "a"
}
[3] =>
array(2) {
'num' =>
string(1) "0"
'let' =>
string(1) "c"
}
}
具有 preg_match_all
(带有 PREG_SET_ORDER
标志)和 array_map
函数的简单解决方案:
$input = '00333b1119a555a0c';
preg_match_all('/([0-9]+?)([a-z]+?)/i', $input, $matches, PREG_SET_ORDER);
$result = array_map(function($v) {
return ['num' => $v[1], 'let' => $v[2]];
}, $matches);
print_r($result);
输出:
Array
(
[0] => Array
(
[num] => 00333
[let] => b
)
[1] => Array
(
[num] => 1119
[let] => a
)
[2] => Array
(
[num] => 555
[let] => a
)
[3] => Array
(
[num] => 0
[let] => c
)
)
您可以使用:
$str = '00333b1119a555a0c';
$arr=array();
if (preg_match_all('/(\d+)(\p{L}+)/', $str, $m)) {
array_walk( $m[1], function ($v, $k) use(&$arr, $m ) {
$arr[] = [ 'num'=>$v, 'let'=>$m[2][$k] ]; });
}
print_r($arr);
输出:
Array
(
[0] => Array
(
[num] => 00333
[let] => b
)
[1] => Array
(
[num] => 1119
[let] => a
)
[2] => Array
(
[num] => 555
[let] => a
)
[3] => Array
(
[num] => 0
[let] => c
)
)
以上所有工作。但他们似乎没有我想要的优雅 - 他们需要循环,使用数组映射,或者(对于 preg_match_all())他们还需要另一个几乎相同的正则表达式,只是为了验证字符串与正则表达式匹配.
我最终发现 preg_match_all() 结合命名捕获 为我解决了这个问题。我以前没有为此目的使用过命名捕获,它看起来很强大。
我还添加了一个可选的额外步骤来简化输出,如果不需要重复(这不在问题中但可能对某人有帮助)。
$input = '00333b1119a555a0c';
preg_match_all("/(?P<num>\d+)(?P<let>[dhm])/", $input, $raw_matches, PREG_SET_ORDER);
print_r($raw_matches);
// if dups not expected this is also worth doing
$matches = array_column($raw_matches, 'num', 'let');
print_r($matches);
带有输入+重复检查的更完整版本
$input = '00333b1119a555a0c';
if (!preg_match("/^(\d+[abc])+$/",$input)) {
// OPTIONAL: detected $input incorrectly formatted
}
preg_match_all("/(?P<num>\d+)(?P<let>[dhm])/", $input, $raw_matches, PREG_SET_ORDER);
$matches = array_column($raw_matches, 'num', 'let');
if (count($matches) != count($raw_matches)) {
// OPTIONAL: detected duplicate letters in $input
}
print_r($matches);
解释:
按照@RomanPerekhrest 和@exussum 的建议,这使用 preg_match_all() 来分解各个组并拆分数字和字母。我使用了命名组,这样 $raw_matches 的结果数组就已经用正确的名称创建了。
但是如果不希望出现重复,那么我使用 array_column() 的额外步骤,它直接从嵌套的条目数组中提取数据并创建所需的平面数组,而不需要循环、映射、行走或逐项分配:from
(group1 => (num1, let1), group2 => (num2, let2), ... )
到"flat"数组:
(let1 => num1, let2 => num2, ... )
如果命名的正则表达式匹配感觉太高级,那么它们可以被忽略 - 匹配无论如何都会被赋予数字,这也同样有效,您将不得不手动分配字母并且更难理解。
preg_match_all("/(\d+)([dhm])/", $input, $raw_matches, PREG_SET_ORDER);
$matches = array_column($raw_matches, 1, 2);
如果您需要检查重复的字母(这不在问题中但可能有用),方法如下:如果原始匹配包含任何字母的 >1 个条目,则当 array_column()使用此字母成为新数组的键,并且不能存在重复键。每个字母只保留一个条目。所以我们只是测试最初找到的匹配数是否与array_coulmn之后的最终数组中的匹配数相同。如果不是,则重复。
我有一个字符串,其正确语法是正则表达式 ^([0-9]+[abc])+$
。因此,有效字符串的示例为:'1a2b' 或 '00333b1119a555a0c'
为清楚起见,字符串是(值、字母)对的列表,顺序很重要。我坚持使用输入字符串,所以我无法更改它。虽然使用上述正则表达式测试正确的语法在原则上似乎很容易,但我试图在 PHP 中想出最有效的方法来将兼容的字符串转换为可用的数组,如下所示:
输入:
'00333b1119a555a0c'
输出:
array (
0 => array('num' => '00333', 'let' => 'b'),
1 => array('num' => '1119', 'let' => 'a'),
2 => array('num' => '555', 'let' => 'a'),
3 => array('num' => '0', 'let' => 'c')
)
我在使用 preg_match 时遇到困难。例如,这没有给出预期的结果,其目的是在 EITHER \d+(并保存)或 [abc](并保存)上进行贪婪匹配,重复直到到达字符串末尾。
$text = '00b000b0b';
$out = array();
$x = preg_match("/^(?:(\d+|[abc]))+$/", $text, $out);
这也不起作用,这里的目的是在 \d+[abc] 上进行贪婪匹配(并保存这些),重复直到到达字符串末尾,然后将它们拆分为数字和字母。
$text = '00b000b0b';
$out = array();
$x = preg_match("/^(?:\d+[abc])+$/", $text, $out);
我计划检查语法作为 preg_match 的一部分,然后使用 preg_match 输出贪婪匹配 'blocks'(或者如果使用 [= 则保留分隔符38=]),然后如果需要,使用 for (...; i+=2)
一次循环遍历结果 2 项以提取成对的值字母。
但我似乎连基本的 preg_split() 或 preg_match() 方法都无法顺利工作,更不用说探索是否有 'neater' 或更有效的方法了方式。
您的正则表达式需要一些匹配组
/([0-9]+?)([a-z])/i
这意味着匹配一组中的所有数字,匹配另一组中的所有字母。 Preg match all 获取所有匹配项。
正则表达式的关键是非贪婪标志 ?
,它匹配尽可能短的字符串。
match[0]
是整场比赛
match[1]
是第一个匹配组(数字)
match[2]
是第二个匹配组(字母)
下面的示例
<?php
$input = '00333b1119a555a0c';
$regex = '/([0-9]+?)([a-z])/i';
$out = [];
$parsed = [];
if (preg_match_all($regex, $input, $out)) {
foreach ($out[0] as $index => $value) {
$parsed[] = [
'num' => $out[1][$index],
'let' => $out[2][$index],
];
}
}
var_dump($parsed);
输出
array(4) {
[0] =>
array(2) {
'num' =>
string(5) "00333"
'let' =>
string(1) "b"
}
[1] =>
array(2) {
'num' =>
string(4) "1119"
'let' =>
string(1) "a"
}
[2] =>
array(2) {
'num' =>
string(3) "555"
'let' =>
string(1) "a"
}
[3] =>
array(2) {
'num' =>
string(1) "0"
'let' =>
string(1) "c"
}
}
具有 preg_match_all
(带有 PREG_SET_ORDER
标志)和 array_map
函数的简单解决方案:
$input = '00333b1119a555a0c';
preg_match_all('/([0-9]+?)([a-z]+?)/i', $input, $matches, PREG_SET_ORDER);
$result = array_map(function($v) {
return ['num' => $v[1], 'let' => $v[2]];
}, $matches);
print_r($result);
输出:
Array
(
[0] => Array
(
[num] => 00333
[let] => b
)
[1] => Array
(
[num] => 1119
[let] => a
)
[2] => Array
(
[num] => 555
[let] => a
)
[3] => Array
(
[num] => 0
[let] => c
)
)
您可以使用:
$str = '00333b1119a555a0c';
$arr=array();
if (preg_match_all('/(\d+)(\p{L}+)/', $str, $m)) {
array_walk( $m[1], function ($v, $k) use(&$arr, $m ) {
$arr[] = [ 'num'=>$v, 'let'=>$m[2][$k] ]; });
}
print_r($arr);
输出:
Array
(
[0] => Array
(
[num] => 00333
[let] => b
)
[1] => Array
(
[num] => 1119
[let] => a
)
[2] => Array
(
[num] => 555
[let] => a
)
[3] => Array
(
[num] => 0
[let] => c
)
)
以上所有工作。但他们似乎没有我想要的优雅 - 他们需要循环,使用数组映射,或者(对于 preg_match_all())他们还需要另一个几乎相同的正则表达式,只是为了验证字符串与正则表达式匹配.
我最终发现 preg_match_all() 结合命名捕获 为我解决了这个问题。我以前没有为此目的使用过命名捕获,它看起来很强大。
我还添加了一个可选的额外步骤来简化输出,如果不需要重复(这不在问题中但可能对某人有帮助)。
$input = '00333b1119a555a0c';
preg_match_all("/(?P<num>\d+)(?P<let>[dhm])/", $input, $raw_matches, PREG_SET_ORDER);
print_r($raw_matches);
// if dups not expected this is also worth doing
$matches = array_column($raw_matches, 'num', 'let');
print_r($matches);
带有输入+重复检查的更完整版本
$input = '00333b1119a555a0c';
if (!preg_match("/^(\d+[abc])+$/",$input)) {
// OPTIONAL: detected $input incorrectly formatted
}
preg_match_all("/(?P<num>\d+)(?P<let>[dhm])/", $input, $raw_matches, PREG_SET_ORDER);
$matches = array_column($raw_matches, 'num', 'let');
if (count($matches) != count($raw_matches)) {
// OPTIONAL: detected duplicate letters in $input
}
print_r($matches);
解释:
按照@RomanPerekhrest 和@exussum 的建议,这使用 preg_match_all() 来分解各个组并拆分数字和字母。我使用了命名组,这样 $raw_matches 的结果数组就已经用正确的名称创建了。
但是如果不希望出现重复,那么我使用 array_column() 的额外步骤,它直接从嵌套的条目数组中提取数据并创建所需的平面数组,而不需要循环、映射、行走或逐项分配:from
(group1 => (num1, let1), group2 => (num2, let2), ... )
到"flat"数组:
(let1 => num1, let2 => num2, ... )
如果命名的正则表达式匹配感觉太高级,那么它们可以被忽略 - 匹配无论如何都会被赋予数字,这也同样有效,您将不得不手动分配字母并且更难理解。
preg_match_all("/(\d+)([dhm])/", $input, $raw_matches, PREG_SET_ORDER);
$matches = array_column($raw_matches, 1, 2);
如果您需要检查重复的字母(这不在问题中但可能有用),方法如下:如果原始匹配包含任何字母的 >1 个条目,则当 array_column()使用此字母成为新数组的键,并且不能存在重复键。每个字母只保留一个条目。所以我们只是测试最初找到的匹配数是否与array_coulmn之后的最终数组中的匹配数相同。如果不是,则重复。