具有相同键和值的php多维数组合并

具有相同键和值的php多维数组合并,php,arrays,Php,Arrays,我花了几个小时试图找到这个问题的答案,但我正在努力。我对PHP和各种内置函数相当熟悉,可以构建一个复杂的foreach()循环来实现这一点,但我想问一下是否有人有更智能的解决方案来解决我的问题 我有以下三行的简化示例数组(实际数组通常更大、更复杂,但问题是相同的) 我想得到的是以下输出: [[ "widget_id" => "widget1", "size" => "large",

我花了几个小时试图找到这个问题的答案,但我正在努力。我对PHP和各种内置函数相当熟悉,可以构建一个复杂的foreach()循环来实现这一点,但我想问一下是否有人有更智能的解决方案来解决我的问题

我有以下三行的简化示例数组(实际数组通常更大、更复杂,但问题是相同的)

我想得到的是以下输出:

[[
    "widget_id" => "widget1",
    "size" => "large",
    "item" => [
        "item_id" => "item1",
        "shape" => "circle",
        "paint" => [[
            "paint_id" => "paint1",
            "colour" => "red",
        ],[
            "paint_id" => "paint2",
            "colour" => "green",
        ]]
    ]
],[
    "widget_id" => "widget2",
    "size" => "medium",
    "item" => [
        "item_id" => "item1",
        "shape" => "circle",
        "paint" => [
            "paint_id" => "paint1",
            "colour" => "red",
        ]
    ]
]]
基本上,当两行共享相同的键和值时,合并它们。当键相同,但值不同时,保留这两个值并将它们放在键下的数字数组中(有点像
array\u merge\u recursive
所做的那样)

挑战在于,值本身可以是数组,并且存在未知数量的级别。有没有一种聪明而有效的方法可以做到这一点,或者我必须求助于一个繁重的
foreach
循环


谢谢你的浏览,希望有比我更聪明的人在读这篇文章

我通过下面的函数获得了预期的数组结构,我希望注释能明确说明其中的内容:

function complex\u merge(数组$arr):数组
{
//分组项目
$result=[];
$iterationKey=0;
//循环浏览每一项
while(($element=array\u shift($arr))!==null){
//按原样保存标量值
$scalarValues=array_filter($element'is_scalar');
//在数组中保存数组值
$arrayValues=数组映射(fn(数组$arrVal)=>[$arrVal],数组过滤器($element,'is_数组'));
$arrayValuesKeys=数组_键($arrayValues);
$result[$iterationKey]=array\u merge($scalarValues,$arrayValues);
//与其他项目进行比较
对于($i=0;$i
只需将
$rows
传递给函数,快速检查值:

echo“”。打印(复杂合并($rows),true)。“”;
/*
显示:
排列
(
[0]=>阵列
(
[widget_id]=>widget1
[大小]=>大
[项目]=>阵列
(
[项目id]=>项目1
[形状]=>圆形
[绘制]=>阵列
(
[0]=>阵列
(
[绘制id]=>绘制1
[颜色]=>红色
)
[1] =>阵列
(
[paint_id]=>paint2
[颜色]=>绿色
)
)
)
)
[1] =>阵列
(
[widget_id]=>widget2
[大小]=>中等
[项目]=>阵列
(
[项目id]=>项目1
[形状]=>圆形
[绘制]=>阵列
(
[绘制id]=>绘制1
[颜色]=>红色
)
)
)
)
*/

以下是我自己的尝试。我想我更喜欢AymDev的版本,更简洁。我想知道哪个更快

$array = ComplexMerge::normalise($array);
用法:


到目前为止您尝试了什么?如果您有多个级别,则可能需要递归function@FelippeDuarte我正在编写一个递归的
foreach()
函数,因为我怀疑没有任何“智能”解决方案(请证明我错了!)。我一做完就发。不会很漂亮的!它可以有更好的var名称,也可能会被优化,但它很有效,我做得很开心:)谢谢你提供了这个很好的例子。我也发布了我自己的尝试,但我认为你的更好。我做了一个快速的基准测试,我的似乎快了约0.01毫秒。但是面向对象的方法更容易使用:)谢谢。实际上,我从你的方法中学到了很多,你使用了一些我不熟悉(或不习惯)的内置方法。也许我会玩一个混合版本,当我有时间,现在我已经实现了你的版本,因为它更短!再次感谢你的帮助。
class ComplexMerge{
    /**
     * Checks to see whether an array has sequential numerical keys (only),
     * starting from 0 to n, where n is the array count minus one.
     *
     * @link https://codereview.stackexchange.com/questions/201/is-numeric-array-is-missing/204
     *
     * @param $arr
     *
     * @return bool
     */
    private static function isNumericArray($arr)
    {
        if(!is_array($arr)){
            return false;
        }
        return array_keys($arr) === range(0, (count($arr) - 1));
    }

    /**
     * Given an array, separate out
     * array values that themselves are arrays
     * and those that are not.
     *
     * @param array $array
     *
     * @return array[]
     */
    private static function separateOutArrayValues(array $array): array
    {
        $valuesThatAreArrays = [];
        $valuesThatAreNotArrays = [];

        foreach($array as $key => $val){
            if(is_array($val)){
                $valuesThatAreArrays[$key] = $val;
            } else {
                $valuesThatAreNotArrays[$key] = $val;
            }
        }

        return [$valuesThatAreArrays, $valuesThatAreNotArrays];
    }

    /**
     * Groups row keys together that have the same non-array values.
     * If every row is already unique, returns NULL.
     *
     * @param $array
     *
     * @return array|null
     */
    private static function groupRowKeysWithSameNonArrayValues($array): ?array
    {
        foreach($array as $key => $row){
            # Separate out the values that are arrays and those that are not
            [$a, $v] = self::separateOutArrayValues($row);

            # Serialise the values that are not arrays and create a unique ID from them
            $uniqueRowId = md5(serialize($v));

            # Store all the original array keys under the unique ID
            $deduplicatedArray[$uniqueRowId][] = $key;
        }

        # If every row is unique, there are no more rows to combine, and our work is done
        if(!$a && count($array) == count($deduplicatedArray)){
            return NULL;
        }

        return $deduplicatedArray;
    }

    private static function mergeRows(array $array): array
    {
        # Get the grouped row keys
        if(!$groupedRowKeys = self::groupRowKeysWithSameNonArrayValues($array)){
            //If there are no more rows to merge
            return $array;
        }

        foreach($groupedRowKeys as $uniqueRowId => $keys){

            foreach($keys as $id => $key){
                # Separate out the values that are arrays and those that are not
                [$valuesThatAreArrays, $valuesThatAreNotArrays] = self::separateOutArrayValues($array[$key]);
                //We're using the key from the grouped row keys array, but using it on the original array

                # If this is the first row from the group, throw in the non-array values
                if(!$id){
                    $unique[$uniqueRowId] = $valuesThatAreNotArrays;
                }

                # For each of the values that are arrays include them back in
                foreach($valuesThatAreArrays as $k => $childArray){
                    $unique[$uniqueRowId][$k][] = $childArray;
                    //Wrap them in a numerical key array so that only children and siblings are have the same parent-child relationship
                }
            }
        }

        # Go deeper
        foreach($unique as $key => $val){
            foreach($val as $k => $valuesThatAreNotArrays){
                if(self::isNumericArray($valuesThatAreNotArrays)){
                    $unique[$key][$k] = self::mergeRows($unique[$key][$k]);
                }
            }
        }

        # No need to include the unique row IDs
        return array_values($unique);
    }

    public static function normalise($array): ?array
    {
        $array = self::mergeRows($array);
        return $array;
    }
}
$array = ComplexMerge::normalise($array);