递归的array_diff()函数是什么?

45
我正在寻找一种工具,可以给我两个数组之间的递归差异。我想象中的是一个带有两个彩色树结构的网页。在每棵树上,绿色表示两个数组中匹配的部分,红色表示彼此不匹配的部分。类似于 dBug 的输出结果。
我有一些代码,可以给我一个嵌套的数组来填充报告。我正在开发一种新方法,应该会更快,但我需要测试值和结构,以确保它的输出与旧方法完全相同。
是否有可用的工具?还是我需要编写代码?或者还有其他实现目标的方法吗?

这只是为了暂时测试这些输出,还是长期使用?如果只是为了测试,那么简单的 wdiff 命令覆盖 var_export 输出就可以解决问题了... - Wrikken
在嵌套结构中,如果一个元素是6个元素的数组,而另一个元素是3个元素的数组,这会使wdiff出现问题吗?因为在输出中,比如从第0行到第30行,它们是相同的,从末尾返回到第36行也是相同的。只有那些中间行是不同的--3与6。如果wdiff查看这个,它会被卡住吗? - user151841
输出结果不会很漂亮地分成键/值对,但它会尝试在后面匹配行以进行进一步的匹配。如果我只是为了检查差异,那么它就足够了。只需使用一个简单的测试脚本来查看是否足够满足您的目的。另一种选择是递归函数,也不难,但需要更多的工作。 - Wrikken
只是一个想法:如果您需要快速的东西,可以首先使用json_encode将其转换为字符串进行测试,比较两个字符串,如果存在差异,则在此之后进入递归检查。 - visualex
6个回答

74

array_diff的注释中实现了这样一个函数。

function arrayRecursiveDiff($aArray1, $aArray2) {
  $aReturn = array();

  foreach ($aArray1 as $mKey => $mValue) {
    if (array_key_exists($mKey, $aArray2)) {
      if (is_array($mValue)) {
        $aRecursiveDiff = arrayRecursiveDiff($mValue, $aArray2[$mKey]);
        if (count($aRecursiveDiff)) { $aReturn[$mKey] = $aRecursiveDiff; }
      } else {
        if ($mValue != $aArray2[$mKey]) {
          $aReturn[$mKey] = $mValue;
        }
      }
    } else {
      $aReturn[$mKey] = $mValue;
    }
  }
  return $aReturn;
} 

该实现一次仅处理两个数组,但我认为这并不会导致问题。如果需要同时比较三个或更多的数组,则可以按顺序运行差异。此外,此方法使用关键字检查和进行宽松验证。


7
@JonL. 我认为他必须是这个问题的作者才能这样做 :) - user151841
7
这个函数返回了大量未发生任何改变的数据。我不明白这有什么用处。 - CommaToast
3
此外,如果您真的想要获取两个数组之间的差异,您需要运行此函数两次,以便不仅找到属于数组1但不属于数组2的部分,还可以找到属于数组2但不属于数组1的部分。对一些人来说可能很明显...但对其他人(比如我刚开始时)可能不是。 - Fabian Becker
比较值行应该多加一个等号,像这样 if ($mValue !== $aArray2[$mKey]) {,这样它也会检测到值类型的变化。 - KoalaBear
1
这个例子实际上是不起作用的。很容易检查 $a = [1,2,3,4]; $b = [4,5,6,7]; arrayRecursiveDiff($a, $b); - klipach
显示剩余4条评论

18

被接受的答案接近正确,但它并没有真正正确地模拟array_diff

存在两个问题,其主要围绕键匹配:

  1. array_diff有一个特定的行为,即如果第二个数组完全缺少数组键,并且该键仍在第二个数组中,则不会为其产生结果。 如果您有两个数组$first = ['foo' => 2, 'moo' => 2]$second = ['foo' => 2],使用被接受的答案的函数,输出将为['moo' => 2]。 如果您将相同的数组通过array_diff运行,它将产生一个空数组。 这是因为上述函数的最终else语句将其添加到差异中,如果数组键不存在,但这不是来自array_diff 期望的行为。对于这两个数组也是如此:$first = ['foo' => 1]$second = [1]array_diff将产生一个空数组。

  2. 如果两个数组具有相同的值但具有不同的键,则返回的值比预期的多。如果您有两个数组$foo = [1, 2]$moo = [2, 1],则被接受的答案的函数将输出来自$foo的所有值。这是因为它在每次迭代中进行严格的键匹配,在其中它找到在两个数组中具有相同键(数字或其他)的情况下,而不是检查第二个数组中的所有其他值。

以下函数类似,但更接近于您对array_diff的预期工作方式(同时使用更少的愚蠢变量名):

function array_diff_recursive($arr1, $arr2)
{
    $outputDiff = [];

    foreach ($arr1 as $key => $value)
    {
        //if the key exists in the second array, recursively call this function 
        //if it is an array, otherwise check if the value is in arr2
        if (array_key_exists($key, $arr2))
        {
            if (is_array($value))
            {
                $recursiveDiff = array_diff_recursive($value, $arr2[$key]);

                if (count($recursiveDiff))
                {
                    $outputDiff[$key] = $recursiveDiff;
                }
            }
            else if (!in_array($value, $arr2))
            {
                $outputDiff[$key] = $value;
            }
        }
        //if the key is not in the second array, check if the value is in 
        //the second array (this is a quirk of how array_diff works)
        else if (!in_array($value, $arr2))
        {
            $outputDiff[$key] = $value;
        }
    }

    return $outputDiff;
}

你能解释一下结果有什么不同吗?对于我的测试,我得到了完全相同的结果。谢谢。 - Jeff Puckett
2
@JeffPuckettII 抱歉我没有给出一个好的解释。我已经更新了答案,解释了被接受的答案与array_diff的不同之处。 - treeface
1
刚刚查看了 http://php.net/manual/en/function.array-diff.php 上的简单示例,对于简单数组它表现如预期(编辑),但递归并未按预期工作。:( - cottton
如果我们的关联数组的值是对象,它就无法正常工作。例如,$a = [ "foo"=>(object)["p1"=>1, "p2"=>2], "moo"=>(object)["p3"=>3]]$b=[ "foo" => (object)["p4"=>4]] - Max Ivak
有时候,将 [1, 2] 和 [2, 1] 视为不同并不是我们想要的。 数组不是集合,元素顺序并不重要。 PHP 数组可以表示很多东西。很难区分关联数组(键可以是任何类型)和普通数组(带整数键)之间的区别。普通数组 [1,2] 被认为与数组 [2,1] 不同。这就是为什么我们有函数来添加元素到数组的开头或结尾,而不仅仅是向数组(集合)中添加元素的原因。 如果你想要将数组看作一个集合,那么就需要另一个函数来比较数组作为集合的情况。 - Max Ivak

7
function array_diff_assoc_recursive($array1, $array2)
{
    foreach($array1 as $key => $value){

        if(is_array($value)){
            if(!isset($array2[$key]))
            {
                $difference[$key] = $value;
            }
            elseif(!is_array($array2[$key]))
            {
                $difference[$key] = $value;
            }
            else
            {
                $new_diff = array_diff_assoc_recursive($value, $array2[$key]);
                if($new_diff != FALSE)
                {
                    $difference[$key] = $new_diff;
                }
            }
        }
        elseif((!isset($array2[$key]) || $array2[$key] != $value) && !($array2[$key]===null && $value===null))
        {
            $difference[$key] = $value;
        }
    }
    return !isset($difference) ? 0 : $difference;
}

例子:

$a = array(
    "product_a" => array(
        'description'=>'Product A',
        'color'=>'Red',
        'quantity'=>'5',
        'serial'=>array(1,2,3)
    ),
    "product_b" => array(
        'description'=>'Product B'
    )
);

$b = array(
    "product_a" => array(
        'description'=>'Product A',
        'color'=>'Blue',
        'quantity'=>'5',
        'serial'=>array(1,2,5)
    ),
    "product_b" => array(
        'description'=>'Product B'
    )
);

输出:

array_diff_assoc_recursive($a,$b);

Array
(
    [product_a] => Array
        (
            [color] => Red
            [serial] => Array
                (
                    [2] => 3
                )    
        )    
)

2
尝试使用这段代码:
function arrayDiffRecursive($firstArray, $secondArray, $reverseKey = false)
{
    $oldKey = 'old';
    $newKey = 'new';
    if ($reverseKey) {
        $oldKey = 'new';
        $newKey = 'old';
    }
    $difference = [];
    foreach ($firstArray as $firstKey => $firstValue) {
        if (is_array($firstValue)) {
            if (!array_key_exists($firstKey, $secondArray) || !is_array($secondArray[$firstKey])) {
                $difference[$oldKey][$firstKey] = $firstValue;
                $difference[$newKey][$firstKey] = '';
            } else {
                $newDiff = arrayDiffRecursive($firstValue, $secondArray[$firstKey], $reverseKey);
                if (!empty($newDiff)) {
                    $difference[$oldKey][$firstKey] = $newDiff[$oldKey];
                    $difference[$newKey][$firstKey] = $newDiff[$newKey];
                }
            }
        } else {
            if (!array_key_exists($firstKey, $secondArray) || $secondArray[$firstKey] != $firstValue) {
                $difference[$oldKey][$firstKey] = $firstValue;
                $difference[$newKey][$firstKey] = $secondArray[$firstKey];
            }
        }
    }
    return $difference;
}

$differences = array_replace_recursive(
    arrayDiffRecursive($firstArray, $secondArray),
    arrayDiffRecursive($secondArray, $firstArray, true)
);
var_dump($differences);

0

Mohamad的答案很好,只是需要在这一行进行更改:

$difference[$newKey][$firstKey] = $secondArray[$firstKey];

使用:

$difference[$newKey][$firstKey] = array_key_exists($firstKey, $secondArray) ? $secondArray[$firstKey] : null;

或者,如果你正在使用 Laravel,可以使用以下代码:

$difference[$newKey][$firstKey] = array_get($secondArray, $firstKey);

否则,你会收到类似以下的错误信息:

PHP 错误:未定义索引:some_key

当 $secondArray 中存在 some_key,但 $firstArray 中不存在该键时。

0

提供的PHP代码中的函数arrayRecursiveDiff()有助于递归地比较关联数组。它遍历第一个数组中的每个元素,并将其与第二个数组中的相应元素进行比较。如果第一个数组中的元素也是一个数组,则该函数会递归地检查这些子数组的差异。该函数从第一个数组中收集所有在第二个数组中不存在(或不同)的唯一元素,包括嵌套数组中的元素,并将它们作为关联数组返回。这实际上是对array_diff()函数的递归版本,它适用于关联数组并支持多维数组。

function arrayRecursiveDiff(array $array1, array $array2): array
{
    $aReturn = [];

    foreach ($array1 as $index => $value) {
        if (array_key_exists($index, $array2)) {
            if (is_array($value) && is_array($array2[$index])) {
                $recursiveDiff = arrayRecursiveDiff($value, $array2[$index]);
                if ($recursiveDiff !== []) {
                    $aReturn[$index] = $recursiveDiff;
                }
            } elseif ($value !== $array2[$index]) {
                $aReturn[$index] = $value;
            }
        } else {
            $aReturn[$index] = $value;
        }
    }

    return $aReturn;
}

$array1 = ['test' => 'test', 'test2' => 'test2', 'test3' => ['test4' => 'test4', 'test5' => 'test5']];
$array2 = ['test' => 'test', 'test2' => 'test2', 'test3' => ['test4' => 'test4', 'test5' => ['test6' => 'test6', 'test7' => 'test7']]];

print_r(arrayRecursiveDiff($array2,$array1));

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