流树变换

时间:2014-11-26 21:23:13

标签: java algorithm java-8

我对Java 8 Streams API的新东西相当新,但是我已经决定将它用于新的功能但是已经碰壁了!

我有一堆MenuItem对象:

MenuItem parent1 = new MenuItem(0L, "Code Parent", "Description Parent");
MenuItem item1 = new MenuItem(1L, "Code1", "Description1");
MenuItem item2 = new MenuItem(2L, "Code2", "Description2");
MenuItem item3 = new MenuItem(3L, "Code3", "Description3");
MenuItem item4 = new MenuItem(4L, "Code4", "Description4");

我还有一堆MenuHierarchy对象,它们代表MenuItem(父/子)之间的层次关系。这个模型是固定的,所以必须使用我所拥有的。

  

构造函数 - MenyHierarchy(id,parent,child,displayOrder)

MenuHierarchy hierarchy1 = new MenuHierarchy(1L, null, parent1);
MenuHierarchy hierarchy2 = new MenuHierarchy(2L, parent1, item1, 1);
MenuHierarchy hierarchy3 = new MenuHierarchy(3L, item1, item2, 2);
MenuHierarchy hierarchy4 = new MenuHierarchy(4L, item2, item3, 3);
MenuHierarchy hierarchy5 = new MenuHierarchy(5L, item3, item4, 4);

MenuHierarchy对象将null父对象视为根节点。

现在使用流API我想使用我创建的MenuNode实体将此关系转换为树状结构:

public class MenuNode implements GenericNode<MenuItem> {

    private MenuItem data;
    private List<GenericNode<MenuItem>> children;

    public MenuNode(MenuItem data) {
        this.data = data;
        this.children = new ArrayList<GenericNode<MenuItem>>();
    }

    // Getters, setters

}

我将解释到目前为止我所拥有的:

        /* This is the list of Root Menus (Menus which have no parent) */
    List<MenuNode> rootNodes = new ArrayList<>();
    List<MenuHierarchy> hierarchyList = Arrays.asList(hierarchy1, hierarchy2, hierarchy3, hierarchy4, hierarchy5);

    /* This first stream adds a new root MenuNode object to the above list ordered by the hierarchy display order */
    hierarchyList.parallelStream()
        .filter((h) -> Objects.isNull(h.getParentMenu()))
        .sorted((h, i) -> h.getDisplayOrder().compareTo(i.getDisplayOrder()))
        .map(MenuHierarchy::getChildMenu)
        .forEachOrdered((i) -> rootNodes.add(new MenuNode(i)));

    /* This second one is where i've sort of failed...
       What i need this to do is iterate over the menu hierarchies and for each non-root one
       add it to the MenuNode children collection where MenuNode.data == MenyHeirarchy.parentMenu
       Resulting in a tree of MenuItems...
     */
    hierarchyList.stream()
        .filter((h) -> Objects.nonNull(h.getParentMenu()))
        .sorted((h, i) -> h.getDisplayOrder().compareTo(i.getDisplayOrder()))
        .forEachOrdered((h) -> {

            rootNodes.stream()
                .filter((n) -> n.getData().equals(h.getParentMenu()))
                .forEach((n) -> {
                    n.getChildren().add(new MenuNode(h.getChildMenu()));
                });

        });

enter image description here

正如您所看到的那样,目前这种情况并不正常,因为它并不代表所有的层次结构......我不确定流是否可以实现这一点?

将极力推荐任何想法。

1 个答案:

答案 0 :(得分:3)

我是否理解你想要最终得到一堆代表菜单的MenuNode对象?如果是这种情况,我认为预先制作所有节点对象然后填充其子列表会更容易。

// first we make all the nodes and map them to ID
Map<Long, MenuNode> nodes = hierarchies.stream()
    .map(MenuHierarchy::getChildMenu)
    .collect(toMap(MenuItem::getId, MenuNode::new));

// and now we go over all hierarchies and add children to appropriate node
hierarchies.stream()
    .filter(h -> h.getParent() != null)
    .sorted(comparing(MenuHierarchy::getDisplayOrder))
    .forEach(h -> {
        long parentId = h.getParentMenu().getId();
        long childId  = h.getChildMenu().getId();
        nodes.get(parentId).getChildren().add(nodes.get(childId))
    });

或者,可以先通过遍历节点来编写第二部分。这样做的好处是可以使MenuNode中的子列表不可变。缺点是您可能会发现重复迭代所有层次结构的想法令人反感(即使它对任何真实的菜单大小都无关紧要):

nodes.values().forEach( node ->
    node.setChildren(
        hierarchies.stream()
            .filter(h -> h.getParentMenu().getId() == node.getData().getId())
            .sorted(comparing(MenuHierarchy::getDisplayOrder))
            .map(MenuHierarchy::getChildMenu)
            .map(MenuItem::getId)
            .map(nodes::get)
            .collect(toList())
    )
);

并且,为了完整起见,您可以使用流对同一父级的层次结构进行分组,并重写第二部分,如下所示:

hierarchies.stream()
    .filter(h -> null != h.getParent())
    .collect(
        groupingBy(h->g.getParentMenu().getId(), toList())
    ) // now we have a map of parent Ids to list of MenuHierarchy for that parent
    .forEach( (parentId, children) ->
        nodes.get(parentId)).setChildren(
            children.stream()
                .sorted(comparing(MenuHierarchy::getDisplayOrder))
                .map(h -> nodes.get(h.getChildMenu().getId()))
                .collect(toList())
        )
    );

你决定什么更清楚。

编辑:我不确定层次结构ID是否始终与子ID相同。如果是,则可以简化代码。