透明地展开一个数组

8

阅读这个问题合并和按多个数组分组,我得到了以下想法:当使用可能重复键的多级数组时,有一个函数可以迭代此类数组,就像它是平面的一样实用。

foreach(flatten($deepArray) as $key => $val)....

你有没有想过如何编写flatten()?是否有标准解决方案?

请注意,由于重复的键,flatten()不能简单地返回一个新的数组。


你可以使用“老式”PHP命名空间技巧,创建一个平面数组,其中每个条目都有一个键,该键与多维数组中的整个路径匹配,例如“level1_level2_..._leveln”=>“value”。 - Pelshoff
1
请查看:RecursiveArrayIterator - Yoshi
请问您能否给出一个输入和输出的例子? - Gordon
1
可能是如何展开多维数组?的重复问题。 - VolkerK
2个回答

13

RecursiveArrayIterator的示例用法。

$array = array( 
    0 => 'a', 
    1 => array('subA','subB',array(0 => 'subsubA', 1 => 'subsubB', 2 => array(0 => 'deepA', 1 => 'deepB'))), 
    2 => 'b', 
    3 => array('subA','subB','subC'), 
    4 => 'c' 
);

foreach (return new RecursiveIteratorIterator(new RecursiveArrayIterator($array))
         as $key => $val) {

    printf(
        '%s: %s' . "\n",
        $key, $val
    );
}

/* Output:
0: a
0: subA
1: subB
0: subsubA
1: subsubB
0: deepA
1: deepB
2: b
0: subA
1: subB
2: subC
4: c
*/
扩展RecursiveIteratorIterator以返回当前键堆栈。
class MyRecursiveIteratorIterator extends RecursiveIteratorIterator
{
  public function key() {
    return json_encode($this->getKeyStack());
  }

  public function getKeyStack() {
    $result = array();
    for ($depth = 0, $lim = $this->getDepth(); $depth < $lim; $depth += 1) {
      $result[] = $this->getSubIterator($depth)->key();
    }
    $result[] = parent::key();
    return $result;
  }
}

foreach ($it = new MyRecursiveIteratorIterator(new RecursiveArrayIterator($array))
         as $key => $val) {

  printf('%s (%s): %s' . "\n", implode('.', $it->getKeyStack()), $key, $val);
}

/* Output:
0 ([0]): a
1.0 ([1,0]): subA
1.1 ([1,1]): subB
1.2.0 ([1,2,0]): subsubA
1.2.1 ([1,2,1]): subsubB
1.2.2.0 ([1,2,2,0]): deepA
1.2.2.1 ([1,2,2,1]): deepB
2 ([2]): b
3.0 ([3,0]): subA
3.1 ([3,1]): subB
3.2 ([3,2]): subC
4 ([4]): c
*/

这是另一种版本,这次不使用RecursiveArrayIterator:

function flatten(array $array = array(), $keyStack = array(), $result = array()) {
  foreach ($array as $key => $value) {
    $keyStack[] = $key;

    if (is_array($value)) {
      $result = flatten($value, $keyStack, $result);
    }
    else {
      $result[] = array(
        'keys' => $keyStack,
        'value' => $value
      );
    }

    array_pop($keyStack);
  }

  return $result;
}

foreach (flatten($array) as $element) {
  printf(
    '%s: %s (depth: %s)' . "\n",
    implode('.', $element['keys']),
    $element['value'],
    sizeof($element['keys'])
  );
}

/*
0: a (depth: 1)
1.0: subA (depth: 2)
1.1: subB (depth: 2)
1.2.0: subsubA (depth: 3)
1.2.1: subsubB (depth: 3)
1.2.2.0: deepA (depth: 4)
1.2.2.1: deepB (depth: 4)
2: b (depth: 1)
3.0: subA (depth: 2)
3.1: subB (depth: 2)
3.2: subC (depth: 2)
4: c (depth: 1)
*/

5
丑?我对它的美感到惊讶。 :( - Yoshi
3
array_walk_recursive($array, function($v, $k) { echo "$k => $v\n"; });的作用与第一种方法相同。请注意,两种方法都无法同时创建新数组并保留键。只有在放弃键时才能返回扁平化的数组。 - Gordon
@hakre 实际上,正如我刚才指出的那样,key必须返回一个字符串,否则将会收到一个Illegal type returned from MyRecursiveIteratorIterator::key()警告。 - Yoshi
@Yoshi:是的,没错。我漏了这个,必须返回标量(http://php.net/manual/en/iterator.key.php)。addcslashes和stripcslashes来拯救。 - hakre
@hakre 把代码稍微改了一下,我觉得这样更有用。(添加了 getKeyStack 并将 key 更改为返回 json 编码的数组)。 - Yoshi
显示剩余2条评论

1

你也可以编写一个简单的遍历函数:

function flatten($node, $fn, $keys = array()) {
    if (! is_array($node)) {
        $fn($node, $keys);
    } else {
        foreach ($node as $k => $v) {
            $new_keys   = $keys;
            $new_keys[] = $k;
            flatten($v, $fn, $new_keys);
        }
    }
}

$array = array( 
    0 => 'a', 
    1 => array('subA','subB',array(0 => 'subsubA', 1 => 'subsubB', 2 => array(0 => 'deepA', 1 => 'deepB'))), 
    2 => 'b', 
    3 => array('subA','subB','subC'), 
    4 => 'c' 
);
// will output: a subA subB subsubA subsubB deepA deepB b subA subB subC c 
flatten($array, function($v, $k) {
    echo $v . ' ';
});

如果您不想每次调用它时都传递另一个函数作为参数,我还编写了一个适配器,它将返回一个数组:

function flatten_array($node) {
    $acc = array();
    flatten($node, function($node, $keys) use (&$acc) {
        $acc[implode('.', $keys)] = $node;
    });
    return $acc;
}

// will spit out the same output as that in Yoshi's answer:
foreach (flatten_array($array) as $k => $v) {
    echo $k .' => ' . $v . "\n";
}

注意:

  • array_walk_recursive 不能使用/不是同一件事情,因为它会跳过保存数组的键。
  • 我用匿名函数编写了我的示例;如果你的 PHP 版本不够新,你必须给函数命名并使用 call_user_func 调用它们。

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