使用声明方法转换数组

时间:2018-04-12 18:05:25

标签: php functional-programming stateless

给出以下代码:

$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等)一样?也无需更改引用或更改相同的变量。如果是这样,怎么样?

1 个答案:

答案 0 :(得分:1)

创建和遍历不同形状的树最好通过使用函数来完成。下面,我们创建函数node_createnode_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

上面,我们了解hasgetset如何简化我们的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_createnode_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 ();
}
相关问题