给出以下代码:
$flat = [
[ '10', 'hoho'],
[ '10', null],
[ '13', null],
[ '10', 'ahha']
];
//imperative, procedural approach
$hierarchical = [];
foreach ($flat as $entry) {
$id = $entry[0];
$hierarchical[$id]['id'] = $id;
$hierarchical[$id]['microtags'] = $hierarchical[$id]['microtags'] ?? [];
if ($entry[1] != null)
array_push($hierarchical[$id]['microtags'], $entry[1]);
}
其结果($ hierarchical):
array (
10 =>
array (
'id' => '10',
'microtags' =>
array (
0 => 'hoho',
1 => 'ahha',
),
),
13 =>
array (
'id' => '13',
'microtags' =>
array (
),
),
)
是否有可能将其重构为合理有效的声明/功能方法?就像使用数组转换函数(map,reduce,filter等)一样?也无需更改引用或更改相同的变量。如果是这样,怎么样?
答案 0 :(得分:1)
创建和遍历不同形状的树最好通过使用函数来完成。下面,我们创建函数node_create
和node_add_child
来编码我们的意图。最后,我们使用array_reduce
来完成转换。 $flat
仍未受影响;我们的还原操作只有从输入数据中读取。
function node_create ($id, $children = []) {
return [ "id" => $id, "children" => $children ];
}
function node_add_child ($node, $child) {
return node_create ($node['id'], array_merge ($node['children'], [ $child ]));
}
$flat =
[ [ '10', 'hoho' ]
, [ '10', null ]
, [ '13', null ]
, [ '10', 'ahha' ]
];
$result =
array_reduce ($flat, function ($acc, $item) {
list ($id, $value) = $item;
if (! array_key_exists ($id, $acc))
$acc [$id] = node_create ($id);
if (! is_null ($value))
$acc [$id] = node_add_child ($acc [$id], $value);
return $acc;
}, []);
结果
print_r ($result);
// Array
// (
// [10] => Array
// (
// [id] => 10
// [children] => Array
// (
// [0] => hoho
// [1] => ahha
// )
// )
// [13] => Array
// (
// [id] => 13
// [children] => Array
// (
// )
// )
// )
上面,我们使用$acc
的关联数组,这意味着我们必须使用PHP的内置函数与关联数组进行交互。我们可以抽象出PHP的丑陋,非功能性接口,以获得更有利的接口。
function has ($map, $key) {
return array_key_exists ($key, $map);
}
function get ($map, $key) {
return $map [$key];
}
function set ($map, $key, $value = null) {
$map [$key] = $value;
return $map;
}
我们将null
个孩子添加到node_add_child
function node_create ($id, $children = []) {
return [ "id" => $id, "children" => $children ];
}
function node_add_child ($node, $child = null) {
if (is_null ($child))
return $node;
else
return node_create ($node['id'], array_merge ($node['children'], [ $child ]));
}
现在我们可以看到更多声明性的reduce
function make_tree ($flat = []) {
return
array_reduce ($flat, function ($acc, $item) {
list ($id, $value) = $item;
return
set ( $acc
, $id
, has ($acc, $id)
? node_add_child (get ($acc, $id), $value)
: node_add_child (node_create ($id), $value)
);
}, []);
}
print_r (make_tree ($flat));
// same output as above
上面,我们了解has
,get
和set
如何简化我们的reduce操作。但是,这种方法可能导致许多小的,分离的功能。另一种方法是发明自己的数据类型以满足您的需求。下面,我们废弃上面创建的分离函数,并将它们交换为类MutableMap
class MutableMap {
public function __construct ($data = []) {
$this->data = $data;
}
public function has ($key) {
return array_key_exists ($key, $this->data);
}
public function get ($key) {
return $this->has ($key)
? $this->data [$key]
: null
;
}
public function set ($key, $value = null) {
$this->data [$key] = $value;
return $this;
}
public function to_assoc () {
return $this->data;
}
}
现在,我们不必将$acc
传递给每个函数,而是将其换成$map
,这是我们新类型的一个实例
function make_tree ($flat = []) {
return
array_reduce ($flat, function ($map, $item) {
list ($id, $value) = $item;
return
$map -> set ( $id
, $map -> has ($id)
? node_add_child ($map -> get ($id), $value)
: node_add_child (node_create ($id), $value)
);
}, new MutableMap ())
-> to_assoc ();
}
当然,您可以将node_create
和node_add_child
换成基于类的实现class Node { ... }
。这个练习留待读者阅读。
function make_tree ($flat = []) {
return
array_reduce ($flat, function ($map, $item) {
list ($id, $value) = $item;
return
$map -> set ( $id
, $map -> has ($id)
? $map -> get ($id) -> add_child ($value)
: (new Node ($id)) -> add_child ($value)
);
}, new MutableMap ())
-> to_assoc ();
}