将嵌套数组中的 "dot notation" 个键扩展到子数组
Expand "dot notation" keys in a nested array to child arrays
我从一个任意深度的嵌套数组开始。在该数组中,一些键是由点分隔的一系列标记。例如 "billingAddress.street" 或 "foo.bar.baz"。我想将这些键控元素扩展为数组,所以结果是一个嵌套数组,所有这些键都被扩展了。
例如:
[
'billingAddress.street' => 'My Street',
'foo.bar.baz' => 'biz',
]
应扩展为:
[
'billingAddress' => [
'street' => 'My Street',
],
'foo' => [
'bar' => [
'baz' => 'biz',
]
]
]
可以将原始 "billingAddress.street" 与新的 "billingAddress" 数组一起保留,但不需要这样做(因此解决方案可以对原始数组进行操作或创建一个新数组)。 "billingAddress.city" 等其他元素可能需要添加到数组的相同扩展部分。
有些键可能有两个以上的标记,用点分隔,因此需要更深入地扩展。
我看过 array_walk_recursive()
但它只对元素起作用。对于每个匹配的元素键,我实际上想修改这些元素所在的父数组。
我看过 array_map
,但它不提供对密钥的访问,据我所知这不是递归的。
要扩展的示例数组:
[
'name' => 'Name',
'address.city' => 'City',
'address.street' => 'Street',
'card' => [
'type' => 'visa',
'details.last4' => '1234',
],
]
这将扩展为:
[
'name' => 'Name',
'address.city' => 'City', // Optional
'address' => [
'city' => 'City',
'street' => 'Street',
],
'address.street' => 'Street', // Optional
'card' => [
'type' => 'visa',
'details.last4' => '1234', // Optional
'details' => [
'last4' => '1234',
],
],
]
我认为我需要的是可以遍历嵌套数组中的每个 array
并可以对其应用用户函数的东西。但我确实怀疑我遗漏了一些明显的东西。我正在使用的支付网关使用点符号向我发送数组和 "pretend arrays" 的组合,我的 objective 是将其规范化为一个数组以提取部分。
我认为这个问题不同于 SO 上的类似问题,因为这种数组和非数组的混合用于扩展。从概念上讲,它是一个嵌套数组,其中 any 级别的 groups 元素需要用新数组替换,因此发生了两个级别的递归这里:树遍历,扩展,然后扩展树的遍历,看看是否需要更多的扩展。
这是一个递归尝试。请注意,这不会删除旧密钥,不会保留任何密钥顺序并忽略 foo.bar.baz
.
类型的密钥
function expand(&$data) {
if (is_array($data)) {
foreach ($data as $k => $v) {
$e = explode(".", $k);
if (count($e) == 2) {
[$a, $b] = $e;
$data[$a][$b]= $v;
}
expand($data[$k]);
}
}
}
结果:
Array
(
[name] => Name
[address.city] => City
[address.street] => Street
[card] => Array
(
[type] => visa
[details.last4] => 1234
[details] => Array
(
[last4] => 1234
)
)
[address] => Array
(
[city] => City
[street] => Street
)
)
解释:
在函数的任何调用中,如果参数是数组,则遍历键和值以查找其中带有 .
的键。对于任何此类键,将它们展开。对数组中的所有键递归调用此函数。
完整版:
这是支持多个 .
s 并随后清理密钥的完整版本:
function expand(&$data) {
if (is_array($data)) {
foreach ($data as $k => $v) {
$e = explode(".", $k);
$a = array_shift($e);
if (count($e) == 1) {
$data[$a][$e[0]] = $v;
}
else if (count($e) > 1) {
$data[$a][implode(".", $e)] = $v;
}
}
foreach ($data as $k => $v) {
expand($data[$k]);
if (preg_match('/\./', $k)) {
unset($data[$k]);
}
}
}
}
@trincot 的另一个解决方案被认为更优雅,这是我现在使用的解决方案。
这是我的解决方案,它扩展了@ggorlen
给出的解决方案和提示
我采用的方法是:
- 创建一个新数组而不是对初始数组进行操作。
- 无需保留旧的预展开元素。如果需要,可以轻松添加它们。
- 从根数组开始,一次一级地展开键,其余的展开递归传回。
class方法:
protected function expandKeys($arr)
{
$result = [];
while (count($arr)) {
// Shift the first element off the array - both key and value.
// We are treating this like a stack of elements to work through,
// and some new elements may be added to the stack as we go.
$value = reset($arr);
$key = key($arr);
unset($arr[$key]);
if (strpos($key, '.') !== false) {
list($base, $ext) = explode('.', $key, 2);
if (! array_key_exists($base, $arr)) {
// This will be another array element on the end of the
// arr stack, to recurse into.
$arr[$base] = [];
}
// Add the value nested one level in.
// Value at $arr['bar.baz.biz'] is now at $arr['bar']['baz.biz']
// We may also add to this element before we get to processing it,
// for example $arr['bar.baz.bam']
$arr[$base][$ext] = $value;
} elseif (is_array($value)) {
// We already have an array value, so give the value
// the same treatment in case any keys need expanding further.
$result[$key] = $this->expandKeys($value);
} else {
// A scalar value with no expandable key.
$result[$key] = $value;
}
}
return $result;
}
$result = $this->expandKeys($sourceArray)
您会发现反转分解组合(点)键得到的键的顺序很有用。在这种相反的顺序中,更容易将先前的结果逐步包装到一个新数组中,从而为一个点 key/value 对创建嵌套结果。
最后,可以使用内置 array_merge_recursive
函数将该部分结果合并到累积的 "grand" 结果中:
function expandKeys($arr) {
$result = [];
foreach($arr as $key => $value) {
if (is_array($value)) $value = expandKeys($value);
foreach(array_reverse(explode(".", $key)) as $key) $value = [$key => $value];
$result = array_merge_recursive($result, $value);
}
return $result;
}
在 repl.it
上查看 运行
我从一个任意深度的嵌套数组开始。在该数组中,一些键是由点分隔的一系列标记。例如 "billingAddress.street" 或 "foo.bar.baz"。我想将这些键控元素扩展为数组,所以结果是一个嵌套数组,所有这些键都被扩展了。
例如:
[
'billingAddress.street' => 'My Street',
'foo.bar.baz' => 'biz',
]
应扩展为:
[
'billingAddress' => [
'street' => 'My Street',
],
'foo' => [
'bar' => [
'baz' => 'biz',
]
]
]
可以将原始 "billingAddress.street" 与新的 "billingAddress" 数组一起保留,但不需要这样做(因此解决方案可以对原始数组进行操作或创建一个新数组)。 "billingAddress.city" 等其他元素可能需要添加到数组的相同扩展部分。
有些键可能有两个以上的标记,用点分隔,因此需要更深入地扩展。
我看过 array_walk_recursive()
但它只对元素起作用。对于每个匹配的元素键,我实际上想修改这些元素所在的父数组。
我看过 array_map
,但它不提供对密钥的访问,据我所知这不是递归的。
要扩展的示例数组:
[
'name' => 'Name',
'address.city' => 'City',
'address.street' => 'Street',
'card' => [
'type' => 'visa',
'details.last4' => '1234',
],
]
这将扩展为:
[
'name' => 'Name',
'address.city' => 'City', // Optional
'address' => [
'city' => 'City',
'street' => 'Street',
],
'address.street' => 'Street', // Optional
'card' => [
'type' => 'visa',
'details.last4' => '1234', // Optional
'details' => [
'last4' => '1234',
],
],
]
我认为我需要的是可以遍历嵌套数组中的每个 array
并可以对其应用用户函数的东西。但我确实怀疑我遗漏了一些明显的东西。我正在使用的支付网关使用点符号向我发送数组和 "pretend arrays" 的组合,我的 objective 是将其规范化为一个数组以提取部分。
我认为这个问题不同于 SO 上的类似问题,因为这种数组和非数组的混合用于扩展。从概念上讲,它是一个嵌套数组,其中 any 级别的 groups 元素需要用新数组替换,因此发生了两个级别的递归这里:树遍历,扩展,然后扩展树的遍历,看看是否需要更多的扩展。
这是一个递归尝试。请注意,这不会删除旧密钥,不会保留任何密钥顺序并忽略 foo.bar.baz
.
function expand(&$data) {
if (is_array($data)) {
foreach ($data as $k => $v) {
$e = explode(".", $k);
if (count($e) == 2) {
[$a, $b] = $e;
$data[$a][$b]= $v;
}
expand($data[$k]);
}
}
}
结果:
Array
(
[name] => Name
[address.city] => City
[address.street] => Street
[card] => Array
(
[type] => visa
[details.last4] => 1234
[details] => Array
(
[last4] => 1234
)
)
[address] => Array
(
[city] => City
[street] => Street
)
)
解释:
在函数的任何调用中,如果参数是数组,则遍历键和值以查找其中带有 .
的键。对于任何此类键,将它们展开。对数组中的所有键递归调用此函数。
完整版:
这是支持多个 .
s 并随后清理密钥的完整版本:
function expand(&$data) {
if (is_array($data)) {
foreach ($data as $k => $v) {
$e = explode(".", $k);
$a = array_shift($e);
if (count($e) == 1) {
$data[$a][$e[0]] = $v;
}
else if (count($e) > 1) {
$data[$a][implode(".", $e)] = $v;
}
}
foreach ($data as $k => $v) {
expand($data[$k]);
if (preg_match('/\./', $k)) {
unset($data[$k]);
}
}
}
}
@trincot 的另一个解决方案被认为更优雅,这是我现在使用的解决方案。
这是我的解决方案,它扩展了@ggorlen
给出的解决方案和提示我采用的方法是:
- 创建一个新数组而不是对初始数组进行操作。
- 无需保留旧的预展开元素。如果需要,可以轻松添加它们。
- 从根数组开始,一次一级地展开键,其余的展开递归传回。
class方法:
protected function expandKeys($arr)
{
$result = [];
while (count($arr)) {
// Shift the first element off the array - both key and value.
// We are treating this like a stack of elements to work through,
// and some new elements may be added to the stack as we go.
$value = reset($arr);
$key = key($arr);
unset($arr[$key]);
if (strpos($key, '.') !== false) {
list($base, $ext) = explode('.', $key, 2);
if (! array_key_exists($base, $arr)) {
// This will be another array element on the end of the
// arr stack, to recurse into.
$arr[$base] = [];
}
// Add the value nested one level in.
// Value at $arr['bar.baz.biz'] is now at $arr['bar']['baz.biz']
// We may also add to this element before we get to processing it,
// for example $arr['bar.baz.bam']
$arr[$base][$ext] = $value;
} elseif (is_array($value)) {
// We already have an array value, so give the value
// the same treatment in case any keys need expanding further.
$result[$key] = $this->expandKeys($value);
} else {
// A scalar value with no expandable key.
$result[$key] = $value;
}
}
return $result;
}
$result = $this->expandKeys($sourceArray)
您会发现反转分解组合(点)键得到的键的顺序很有用。在这种相反的顺序中,更容易将先前的结果逐步包装到一个新数组中,从而为一个点 key/value 对创建嵌套结果。
最后,可以使用内置 array_merge_recursive
函数将该部分结果合并到累积的 "grand" 结果中:
function expandKeys($arr) {
$result = [];
foreach($arr as $key => $value) {
if (is_array($value)) $value = expandKeys($value);
foreach(array_reverse(explode(".", $key)) as $key) $value = [$key => $value];
$result = array_merge_recursive($result, $value);
}
return $result;
}
在 repl.it
上查看 运行