【问题标题】:Why is array_merge_recursive not recursive?为什么 array_merge_recursive 不是递归的?
【发布时间】:2018-06-20 12:14:28
【问题描述】:

我最近在我的应用程序中发现了一个由array_merge_recursive 的意外行为引起的错误。我们来看看这个简单的例子:

$array1 = [
    1 => [
        1 => 100,
        2 => 200,
    ],
    2 => [
        3 => 1000,
    ],
    3 => [
        1 => 500
    ]
];
$array2 = [
    3 => [
        1 => 500
    ]
];
array_merge_recursive($array1, $array2);
//returns: array:4 [ 0 => //...

我希望得到一个包含 3 个元素的数组:键 1、2 和 3。但是,该函数返回一个包含键 0、1、2 和 3 的数组。所以 4 个元素,而我期望只有 3 个。当我将数字替换为对应的字母(a、b、c),它返回一个只有 3 个元素的数组:a、b 和 c。

$array1 = [
    'a' => [
        1 => 100,
        2 => 200,
    ],
    'b' => [
        3 => 1000,
    ],
    'c' => [
        1 => 500
    ]
];
$array2 = [
    'c' => [
        1 => 500
    ]
];
array_merge_recursive($array1, $array2);
//returns: array:3 [ 'a' => //...

这是(至少对我而言)意想不到的行为,但至少它被记录在案:

http://php.net/manual/en/function.array-merge-recursive.php

如果输入数组具有相同的字符串键,则 这些键被合并到一个数组中,这样就完成了 递归地,因此如果其中一个值是数组本身,则 函数会将其与另一个数组中的相应条目合并 也。但是,如果数组具有相同的数字键,则后面的 value 不会覆盖原始值,而是会追加

文档对“附加”的含义不是很清楚。事实证明,$array1 中带有数字键的元素将被视为索引元素,因此它们将丢失当前键:返回的数组以 0 开头。当同时使用数字键和字符串键时,这将导致奇怪的结果一个数组,但如果您使用这样的不良做法,我们不要责怪 PHP。就我而言,问题是通过使用array_replace_recursive 来解决的,这达到了预期的效果。 (该函数中的“替换”表示如果存在则替换,否则追加;命名函数很难!)

问题一:递归与否?

但这并不是这个问题的结束。我认为array_*_resursive 将是一个递归函数:

递归是一种函数调用,其中函数调用自身。 此类函数也称为递归函数。结构 递归是一种解决问题的方法,其中的解决方案 问题取决于同一问题的较小实例的解决方案。

事实证明并非如此。虽然$array1$array2 是关联数组,但上面示例中的$array1['c']$array2['c'] 都是具有一个元素的索引数组:[1 => 500]。让我们合并它们:

array_merge_recursive($array1['c'], $array2['c']);
//output: array:2 [0 => 500, 1 => 500]

这是预期的输出,因为两个数组都有一个数字键 (1),所以第二个将附加到第一个。新数组从键 0 开始。但让我们回到第一个示例:

array_merge_recursive($array1, $array2);
// output:
// array:3 [
//  "a" => array:2 [
//    1 => 100
//    2 => 200
//  ]
//  "b" => array:1 [
//    3 => 1000
//  ]
//  "c" => array:2 [
//    1 => 500 //<-- why not 0 => 500?
//    2 => 500
//  ]
//]

$array2['c'][1] 附加到$array1['c'],但它有键 1 和 2。不是前面示例中的 0 和 1。在处理整数键时,主数组及其子数组的处理方式不同。

问题 2:字符串或整数键有很大的不同。

在写这个问题的时候,我发现了一些别的东西。用子数组中的字符串键替换数字键时会变得更加混乱:

$array1 = [
    'c' => [
        'a' => 500
    ]
];
$array2 = [
    'c' => [
        'a' => 500
    ]
];
array_merge_recursive($array1, $array2);
// output:
// array:1 [
//  "c" => array:1 [
//    "a" => array:2 [
//      0 => 500
//      1 => 500
//    ]
//  ]
//]

所以使用字符串键会将(int) 500 转换为array(500),而使用整数键则不会。

有人可以解释这种行为吗?

【问题讨论】:

标签: php arrays recursion array-merge


【解决方案1】:

如果我们退后一步,观察array_merge*() 函数在仅使用一个数组时的行为,那么我们将了解它如何以不同的方式处理关联数组和索引数组:

$array1 = [
    'k' => [
        1 => 100,
        2 => 200,
    ],
    2 => [
        3 => 1000,
    ],
    'f' => 'gf',
    3 => [
        1 => 500
    ],
    '99' => 'hi',
    5 => 'g'
];

var_dump( array_merge_recursive( $array1 ) );

输出:

array(6) {
  ["k"]=>
  array(2) {
    [1]=>
    int(100)
    [2]=>
    int(200)
  }
  [0]=>
  array(1) {
    [3]=>
    int(1000)
  }
  ["f"]=>
  string(2) "gf"
  [1]=>
  array(1) {
    [1]=>
    int(500)
  }
  [2]=>
  string(2) "hi"
  [3]=>
  string(1) "g"
}

如您所见,它获取了所有数字键并忽略了它们的实际值,并按照遇到它们的顺序将它们返回给您。我想该函数这样做是为了在底层 C 代码中保持理智(或效率)。

回到您的两个数组示例,它采用$array1 的值,对它们进行排序,然后然后 附加$array2

这种行为是否合理是一个完全独立的讨论......

【讨论】:

  • 你说得对,这是实际行为。但是为什么$array 的(数字)键的实际值被忽略了,而$array['k']$array[1] 的(也是数字的)键却没有呢?这种方式听起来不像递归。
  • @StephanVierkant 你说得对,理论上这些子数组应该重新编号……还是主数组不应该重新编号?
  • @StephanVierkant PHP 7 似乎真的把array_merge_recursive() 搞砸了。我提交了错误报告;见bugs.php.net/bug.php?id=76505
【解决方案2】:

您应该阅读您提供的链接(强调我的):

如果输入数组具有相同的字符串键,则这些键的值合并到一个数组中,这是递归完成的,因此如果一个的值是一个数组本身,该函数也会将它与另一个数组中的相应条目合并。 但是,如果数组具有相同的数字键,则后面的值不会覆盖原始值,而是将被追加。。 p>

【讨论】:

  • 这并不能解释为什么用数字键合并两个数组会导致数组以 0 开头,但是如果其中一个值是数组本身,则原始元素将保留它的键。跨度>
  • 我对您的回答投了反对票,因为它没有回答问题。我在我的问题中得到了完全相同的引用,并强调了几乎相同的词。
  • @StephanVierkant,那么您应该向维护 PHP 的小组提出您的问题和投诉。 array_merge_recursive 是 PHP 语言的一部分。 SO 是质疑 PHP 的维护者如何实现该语言的错误论坛。
猜你喜欢
  • 1970-01-01
  • 2013-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多