PHP如何将嵌套数组转换为单一数组并连接键名?

12

这是一个示例数组:

 $foo = array(
           'employer' => array(
                    'name' => 'Foobar Inc',
                    'phone' => '555-555-5555'
                     ),
           'employee' => array(
                    'name' => 'John Doe',
                    'phone' => '555-555-5556',
                    'address' => array(
                           'state' => 'California',
                           'zip' => '90210'
                        )
                    ),
           'modified' => '2009-12-01',
         );

我想要得到这样的结果:

$fooCompressed = array(
             'employer_name' => 'Foobar Inc',
             'employer_phone' => '555-555-5555',
             'employee_name' => 'John Doe',
             'employee_phone' => '555-555-5556'
             'employee_address_state' => 'California',
             'employee_address_zip' => '90210',
             'modified' => '2009-12-01'
             )

我应该如何编写一个递归函数来处理这个问题?

8个回答

17

就像这样:

function makeNonNestedRecursive(array &$out, $key, array $in){
    foreach($in as $k=>$v){
        if(is_array($v)){
            makeNonNestedRecursive($out, $key . $k . '_', $v);
        }else{
            $out[$key . $k] = $v;
        }
    }
}

function makeNonNested(array $in){
    $out = array();
    makeNonNestedRecursive($out, '', $in);
    return $out;
}

// Example
$fooCompressed = makeNonNested($foo);

+1 这非常接近我会做的事情。因为键正在被修改,所以没有内置函数可以为您完成此操作,而且您绝对需要递归来穿过任何子值,这些子值也是数组。 - zombat

8

我认为使用http_build_query这个“技巧”可以更加简洁,而且不需要使用递归(或者至少让php来帮助你完成)

如果你的str_replace使用url编码的值替换[和],那么只需要3行代码即可。

$string      = http_build_query($array);
$string      = urldecode($string);
$string      = str_replace(
                    array('[',']'),
                    array('_','') , 
                    $string
                );
parse_str($string, $flat_array);

$flat_array变成了:

array(7) {
  ["employer_name"]         =>"Foobar Inc"
  ["employer_phone"]        =>"555-555-5555"
  ["employee_name"]         =>"John Doe"
  ["employee_phone"]        =>"555-555-5556"
  ["employee_address_state"]=>"California"
  ["employee_address_zip"]  =>"90210"
  ["modified"]              =>"2009-12-01"
}

4

这里有一个函数,允许您通过第二个参数指定顶级前缀:

function flatten_array($array, $prefix = null) {
  if ($prefix) $prefix .= '_';

  $items = array();

  foreach ($array as $key => $value) {
    if (is_array($value))
      $items = array_merge($items,  flatten_array($value, $prefix . $key));
    else
      $items[$prefix . $key] = $value;
  }

  return $items;
}

3

我更喜欢的方法与此处发布的一些方法非常相似,但并不相同。我在一个重复的帖子中发现了这个方法:https://dev59.com/VGkw5IYBdhLWcg3w8fBJ#9546215 作者是"Felix Kling"。

他的代码将数组键展开为带有点连接的单维数组键,这意味着数字数组将创建自己的"关键路径"。这非常有用,但在大量相似项的数组中可能会产生许多无意义的相似路径。

function flatten($array, $prefix = '') {
    $result = array();
    foreach($array as $key=>$value) {
        if(is_array($value)) {
            $result = $result + flatten($value, $prefix . $key . '.');
        }
        else {
            $result[$prefix . $key] = $value;
        }
    }
    return $result;
}

在我的情况下,我还需要将“唯一喜欢”的路径展平为数组键,并提供一些数据样本以进行规范化。因此,我扩展了他的方法,添加了一个可选的数字键压缩参数。还添加了可选的参数分隔符配置。
主要目的是使键结构和路径相关数据易于分析。我认为,在进一步的完整数据操作中,如果任务是键映射,则此方法非常有用。
/**
* Convert a multidimensional array into a single dimension array.
* Nested array keys will be concatenated with the $separator string
* Numeric keys can also be flattened in a "unique key" array style with $numeric_squash 
* If $numeric_squash is true, numeric array keys are concatenated with $numeric_squash_separator, 
* for later detection and processing if necessary. "[*]" by default.
* If $numeric_squash_separator is set to false, the array key is flattened so that the values 
* would be displayed as if there were no numeric array.
*
* array  $array                    : Array to be flattened
* string $prefix                   : String to prepend on flattened keys
* string $separator                : String concatenated between nested array keys.
* bool   $numeric_squash           : Squash numeric array keys
* string $numeric_squash_separator : String replacing numeric keys, none if false

*/  

public static function array_flatten($array, $prefix = '', $separator = '.' , $numeric_squash = false , $numeric_squash_separator = '[*]') {
    $result = array();
    foreach($array as $key => $value) {
        if(is_array($value)) {
            if($numeric_squash && is_numeric($key))
                $n_key = $numeric_squash_separator ? $numeric_squash_separator . $separator: '';
            else
                $n_key = $key . $separator;

            $result = $result + self::array_flatten($value, $prefix . $n_key  , $separator , $numeric_squash , $numeric_squash_separator);
        }
        else {
            $result[$prefix . ($numeric_squash && is_numeric($key) ? '' : $key)] = $value;
        }
    }
    return $result;
}

同时也要说,这个函数并没有进行性能优化,可以在numeric_squash上节省迭代次数,还有一些比较操作也可以优化。


1

使用仅包含array_* PHP函数和递归的解决方案:

<?php
$array = array(
    "level1"=>"value",
    "level2" => ["level11" => "value", "level21" => "value"],
    "level3" => ["level2" => ["level1" => "value"]],
    "level4" => ["level3" => ["level2" => ["level1" => "value"]]],
    "level5" => ["level4" => ["level3" => ["level2" => ["level1" => "value"]]]],
);
class GharbiFlat {
 
    /**
     * flatten array with combined keys
     */
    public function arrayFlat($array, $keySeparator = '_')
    {
        $result = [];
        array_walk(
            $array,
            function ($v, $pk) use (&$result, $keySeparator) {
                if (is_array($v)) {
                    $result += $this->arrayFlat(
                        array_combine(
                            array_map(
                                function ($k) use ($pk, $keySeparator) {
                                    return $pk . $keySeparator . $k;
                                },
                                array_keys($v)
                            ),
                            $v
                        ),
                        $keySeparator
                    );
                } else {
                    $result[$pk] = $v;
                }
            }
        );
        return $result;
    }
}

$example = new GharbiFlat();

print_r($example->arrayFlat($array));

输出:

Array
(
    [level1] => value
    [level2_level11] => value
    [level2_level21] => value
    [level3_level2_level1] => value
    [level4_level3_level2_level1] => value
    [level5_level4_level3_level2_level1] => value
)

0
经过几次迭代,我已经能够优化这个问题的解决方案,采用基于栈的方法避免递归,从而简化了一些事情。
/***
 * @name array_flatten
 * @author Tom Penzer @tpenzer
 * Flattens a multi-tiered array into a single-tiered 
 * associative array with keys reflective of their 
 * values' hierarchy.
 *
 * @param    array    $array       Required - the multi- 
 * level keyed array to be flattened
 * @param    string   $separator   Optional - the string 
 * used to separate the keys from different levels of 
 * the hierarchy
 *
 * @return   array    a single-level keyed array
 ***/
function array_flatten($array, $separator = '_') {
    $output = array();

    while (list($key, $value) = each($array)) {
        if (is_array($value)) {
            $build = array();
            foreach ($value as $s_key => $s_value) {
                $build[$key . $separator . $s_key] = $s_value;
            }
            unset($array[$key]);
            $array = $build + $array;
            unset($build);
            continue;//skip write to $output
        }
        $output[$key] = $value;
        unset($array[$key]);
    }

    return $output;
}

虽然不完全是所要求的方法,但它与递归解决问题的方式形成了很好的对比。


0
/**
 * Flatten a multi-dimensional array or a nested object, constructing concatenated keys for
 *    nested elements.
 * @param array or object $array - the array or object to be flattened
 * @param array or string $key_path - current parent keys path.
 *    Pass this parameter as string if you need to set a common prefix for all keys 
 * @param string $level_separator - keys concatenation glue
 * @param array $flat - resulting flattened array (omit this parameter when calling the function)
 * @return single-dimensional array with all array keys as concatenated keys of elements' 
 *    paths through the data structure
 */
 function flattenArray($array, &$key_path = array(), $level_separator = '.', &$flat = array())
 {
      if(!is_array($key_path))
      {
           // sanitize key_path
           $key_path = array((string)$key_path);
       }
       foreach($array as $key => $value)
       {
            // push current key to path
            array_push($key_path, $key);

            if(is_array($value) || is_object($value))
            {
                 // next level recursion
                 $flat = array_merge($flat, flattenArray($value, $key_path, $level_separator, $flat));
             }
             else
             {
                  // write the value directly
                  $flat[implode($level_separator, $key_path)] = $value;
              }

              // remove used key
              array_pop($key_path);
        }

        return $flat;
  }

-2
这将把多维关联数组压平,并在键名重复时将数字附加到键名。如果您不介意使用数字索引来区分重复的键名,而不是连接的键名,则可以使用此解决方案。
$result = array();
array_walk_recursive($your_array, function($v, $k) use (&$result){ $i = ""; for (; isset($result[$k."$i"]); $i++); $result[$k."$i"] = $v; });

我怀疑它可以进一步改进以实现连接键。

上述解决方案基本上是为了做这种事情而设计的。

<?php
    $xml_str = "
    <images>
        <image>
            <position>0</position>                  
        </image>
        <image1>
            <position>10</position>
        </image1>
    </images>";
                    // turn the xml into a multidimentional array
            $ob = simplexml_load_string($xml_str);
            $json = json_encode($ob);
            $my_array = json_decode($json, true);

            print_r($my_array);
                    // flatten it
            $result = array();
            array_walk_recursive($my_array, function($v, $k) use (&$result){ $i = ""; for (; isset($result[$k."$i"]); $i++); $result[$k."$i"] = $v; });

            print_r($result);
?>

1
array_walk_recursive() 忽略所有非叶节点。https://3v4l.org/Ca4m2 这种技术不可能为此问题提供所需的输出。换句话说,这是针对不同问题的正确答案。 - mickmackusa

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