数组合并递归旧值与新值

Posted

技术标签:

【中文标题】数组合并递归旧值与新值【英文标题】:Array Merge Recursive old vs new values 【发布时间】:2021-10-22 11:45:52 【问题描述】:

我想合并两个数组来比较新旧值。例如,$arr1 是旧值 $arr2 是新值。

如果数据被删除 $arr2 是一个空数组。示例:

$arr1 =  [
  "databases" => [
    0 => [
      "id" => 1
      "name" => "DB1"
      "slug" => "db1"
      "url" => "https://www.db1.org"
    ]
  ]
];

$arr2 = [];

为此,我在合并后的预期输出是

$merged = [
    "databases" => [
        0 => [
            "id" => [
                'old' => 1,
                'new' => null
            ],
            "name" => [
                'old' => "DB1",
                'new' => null
            ],
            "slug" => [
                'old' => "db1",
                'new' => null
            ],
            "url" => [
                'old' => "https://www.db1.org",
                'new' => null
            ],
        ]
    ]
];

如果 arr2 不同,则值应出现在 new 字段中,而不是 null。

例如:

$arr1 =  [
  "databases" => [
    0 => [
      "id" => 1
      "name" => "DB1"
      "slug" => "db1"
      "url" => "https://www.db1.org"
    ]
  ]
];

$arr2 =  [
  "databases" => [
    0 => [
      "id" => 5
      "name" => "DB2"
      "slug" => "db2"
      "url" => "https://www.db2.com"
    ]
  ]
];

预期输出:

$merged = [
    "databases" => [
        0 => [
            "id" => [
                'old' => 1,
                'new' => 5
            ],
            "name" => [
                'old' => "DB1",
                'new' => "DB2"
            ],
            "slug" => [
                'old' => "db1",
                'new' => "db2"
            ],
            "url" => [
                'old' => "https://www.db1.org",
                'new' => "https://www.db2.com"
            ],
        ]
    ]
];

情况 3 是 $arr1 为空但 $arr2 已填充:

$arr1 = [];

$arr2 =  [
  "databases" => [
    0 => [
      "id" => 1
      "name" => "DB1"
      "slug" => "db1"
      "url" => "https://www.db1.org"
    ]
  ]
];

预期的输出是:

$merged = [
    "databases" => [
        0 => [
            "id" => [
                'old' => null,
                'new' => 1
            ],
            "name" => [
                'old' => null,
                'new' => "DB1"
            ],
            "slug" => [
                'old' => null,
                'new' => "db1"
            ],
            "url" => [
                'old' => null,
                'new' => "https://www.db1.org"
            ],
        ]
    ]
];

内置的 php 函数无法格式化 oldnew 格式的数据,所以想知道如何解决这个问题?任何解决方案/建议将不胜感激。

更新

这是我之前尝试过的:

    我尝试过简单的array_merge_recursive,但它不存储源数组。因此,如果您没有 $arr1 键,则最终合并的数组将只有一个值。 我尝试了一些更多的递归函数深夜但失败了,所以基本上没有任何东西可以显示我尝试过的东西。但是,今天早上,我想出了解决方案并将其发布为答案,以防有人需要使用它。

【问题讨论】:

嗨!目前,这个问题读起来有点像希望有人为你做你的工作。如果您自己尝试一下,并展示您想出的代码以及您认为哪里出了问题,您可能会得到更好的帮助。 @IMSoP 抱歉,我放弃并发布此内容时已是深夜。现在我又读了一遍,是的,听起来确实如此。我已经想出了一个解决方案,并将其作为答案发布,以防其他人需要这个。我只需要再测试一下。 注册。 “怎么办?”:为什么不设置两个数组,一个用于old,另一个用于new。然后获取密钥并进行各种操作。 + 数组运算符可能会派上用场。只是一些想法。 【参考方案1】:

有趣的问题,只要一侧的(非空)数组意味着要遍历它,并且任何 skalar 或 null 都是终止节点(而如果任何旧的或新的数组将强制遍历更深,所以丢弃另一个非数组值):

它的工作原理是递归地在一个数组上映射旧的和新的,当决定遍历以提供null 值时,以防在迭代两者的键的超集时键成员不可用null 表示没有键:

$keys = array_unique(array_merge(array_keys($old ?? []), array_keys($new ?? [])));

$merged = [];
foreach ($keys as $key) 
    $merged['old'] = $old[$key] ?? null;
    $merged['new'] = $new[$key] ?? null;

然后可以递归应用,我发现将$old$new 处理为['old' => $old, 'new' => $new] 更容易,因为可以递归合并相同的结构:

function old_and_new(array $old = null, array $new = null): array

    $pair = get_defined_vars();
    $map =
        static fn(callable $map, array $arrays): array => in_array(true, array_map('is_array', $arrays), true)
        && ($parameter = array_combine($k = array_keys($arrays), $k))
        && ($keys = array_keys(array_flip(array_merge(...array_values(array_map('array_keys', array_filter($arrays, 'is_array'))))))
        )
            ? array_map(
                static fn($key) => $map($map, array_map(static fn($p) => $arrays[$p][$key] ?? null, $parameter)),
                array_combine($keys, $keys)
            )
            : $arrays;

    return $map($map, $pair);


print_r(old_and_new(new: $arr2));

在线演示:https://3v4l.org/4KdLs#v8.0.9


内部在技术上适用于两个以上的数组,例如三。它向上“移动”数组键,类似于转置操作。顺便说一句。有一个类似的问题(但只是类似的,对于您的问题中有趣的部分,它没有得到回答,我的回答在这里并不直接适用):

Transposing multidimensional arrays in PHP

【讨论】:

感谢您的解决方案。但如果数组的深度不同,这将失败。因此,如果“数据库”的级别嵌套更深一层,它将失败。 @Coola:是的,这是该建议的一个限制,因为必须映射遍历(按深度)才能将其映射。我现在把它变成了一个递归函数,它实际上更容易了,但是我没有太多时间来写描述,但你已经可以找到更新的答案了。 谢谢哈克雷。另外它与我下面的答案相比如何?两者都有什么优势吗? 它做不同的事情:把 old+new 变成一个数组 first 然后遍历它。构建唯一键组(您的 array_keys($old) + array_keys($new) 可能会看到您不希望键的副作用),允许严格不需要中间体(旧、新 1、新 2、新 3、最新),但它概括了可以提供稳定性的代码.直接比较,通过使用一个而不是两个(或三个等)参数来简化递归。否则:这个示例代码风格真的不是很可读。它应该有一个版本,可以用 foreach 等显示它并进行比较。【参考方案2】:

在查看我自己的代码后,这是我想出的解决方案。我在这里发布它以防其他人需要解决方案:

  /**
   * Function to merge old and new values to create one array with all entries
   *
   * @param array $old
   * @param array $new
   * @return void
   */
  function recursiveMergeOldNew($old, $new) 
    $merged = array();

    $array_keys = array_keys($old) + array_keys($new);
    
    if($array_keys) 
      foreach($array_keys as $key) 
        $oldChildArray = [];
        $newChildArray = [];
        if(isset($old[$key])) 
          if(!is_array($old[$key])) 
            $merged[$key]['old'] = $old[$key];
           else 
            $oldChildArray = $old[$key];
          
         else 
          $merged[$key]['old'] = null;
        
        
        if(isset($new[$key])) 
          if( !is_array($new[$key])) 
            $merged[$key]['new'] = $new[$key];
           else 
            $newChildArray = $new[$key];
          
         else 
          $merged[$key]['new'] = null;
        

        if($oldChildArray || $newChildArray) 
          $merged[$key] = recursiveMergeOldNew($oldChildArray, $newChildArray);
        
      
    

    return $merged;
  

注意 - 此解决方案需要测试。

【讨论】:

以上是关于数组合并递归旧值与新值的主要内容,如果未能解决你的问题,请参考以下文章

如果在更新触发之前未在 oracle 中进行更新,则新值与旧值相同

js递归(二)——合并多维数组

关联数组的合并递归问题

使用没有第三个数组的递归合并两个排序数组

array_merge_recursive — 递归地合并一个或多个数组

array_merge_recursive — 递归地合并一个或多个数组