【问题标题】:Expand "dot notation" keys in a nested array to child arrays将嵌套数组中的“点表示法”键扩展到子数组
【发布时间】:2019-01-05 11:24:18
【问题描述】:

我从一个任意深度的嵌套数组开始。在该数组中,一些键是一系列由点分隔的标记。例如“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 并可以对其应用用户函数的东西。但我确实怀疑我遗漏了一些明显的东西。我正在使用的支付网关使用点符号向我发送这种数组和“假装数组”的组合,我的目标是将其规范化为一个数组以提取部分。

我相信这个问题与 SO 上的类似问题不同,因为这种数组和非数组的混合用于扩展。从概念上讲,它是一个嵌套数组,其中 any 级别的声音 groups 元素需要替换为新数组,因此这里发生了两个级别的递归:树行走,和扩展,然后遍历扩展的树,看看是否需要更多的扩展。

【问题讨论】:

  • 我们能保证最多一个. 还是我们会看到foo.bar.baz?如果是后者,应该如何处理——作为barbaz子或兄弟?
  • @NigelRen 子结构分散在现有的嵌套数组中,因此这是关于扩展数组中的节点,而不是从头开始构建数组元素的机制。答案是最有启发性的,提供的解决方案以一种新的方式解决了我的问题。

标签: php arrays recursion adyen


【解决方案1】:

您会发现颠倒组合键(虚线键)得到的键顺序很有用。以这种相反的顺序,更容易将先前的结果逐步包装到新数组中,从而为一个点分键/值对创建嵌套结果。

最后,可以使用内置的array_merge_recursive 函数将该部分结果合并到累积的“大”结果中:

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上运行

【讨论】:

  • 这太棒了!不过,我意识到像'foo.bar' => [ 'avocado' ], 'foo.bar.baz' => [ 'banana' ] 这样的键会出现问题,这让它有点烦人。我不确定 OP 在这种边缘情况下需要多么强大。
  • @ggorlen,感谢您的评论。据我所见,您和我的解决方案都为您提到的边缘情况产生了相同的结果,我认为这是最合适的结果,因为它在输出中同时保留了“鳄梨”和“香蕉”。在 OP 自己的解决方案中,“鳄梨”的价值会丢失。
  • 它仍然存在,但索引到了 0 的键,因此似乎需要使用正确的键创建新列表。这是另一种情况:foo.bar.baz => "banana", foo.bar.baz => "avocado" 其中"avocado" 将覆盖"banana"。我进一步搞砸了,但现在放弃了:)
  • 我不会担心最后一个例子:它们(即foo.bar.baz)是键,因此它们在定义上应该是唯一的。这与标准 PHP 数组字面量相同:["key" => 1, "key" => 4] 将生成一个包含 1 个键/值对的数组。那里没有真正的问题。对于我们首先讨论的情况:["avocado"] 确实是 ["0" => "avocado"] 的简写形式,所以对我来说结果有一个 0 键似乎完全没问题。
  • 这是一个很好的解决方案 - array_merge_recursive() 是一个被严重低估的功能。您的示例鳄梨边缘情况不会在这里发生 - 如果有 foo.bar.bazfoo.bar 将始终表示源数据中的数组。源数据像这样是一致的,但是当它通过 API 传给我时会经历这种内爆。
【解决方案2】:

这是一个递归尝试。请注意,这不会删除旧键,不会维护任何键顺序并忽略 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]);
      }
    }
  }
}

【讨论】:

  • 谢谢。 foo.bar.baz 也需要处理。我会玩这个,也许是返回一个新数组而不是修改原始数组。我想我缺少的是我需要一个可以调用自己的函数,因为我希望一个标准的 PHP 函数可以为我做递归的事情。
  • 没问题,但请参阅我对您的问题的评论——foo.bar.baz 是否扩展为一系列孩子或兄弟姐妹?
  • 一系列孩子['foo']['bar'] => 'baz'
  • 听起来不错。如果您使用预期 i/o 的具体示例更新您的帖子会很有帮助,因为这是一个非常重要的约束。
  • 当然,听起来不错。我在想的另一件事是任何嵌套的子级都可以以字符串格式附加,以便在达到深度时自动处理它们。例如。 foo.bar.baz 在当前范围内创建一个名为 foo 的键,并附加一个名为 bar.baz 的子键并带有适当的值。下一个递归调用将解包。
【解决方案3】:

@trincot 的另一个解决方案被认为更优雅,是我现在正在使用的解决方案。

这是我的解决方案,它扩展了@ggorlen 给出的解决方案和提示

我采取的方法是:

  • 创建一个新数组而不是对初始数组进行操作。
  • 无需保留旧的预扩展元素。如果需要,可以轻松添加它们。
  • 从根数组开始,每次展开一个级别的键,其余的展开以递归方式传回。

类方法:

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)

【讨论】:

    猜你喜欢
    • 2013-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-08
    相关资源
    最近更新 更多