zoukankan      html  css  js  c++  java
  • 常见的树形结构封装

      在日常的开发工作中,时常会遇到树形结构的封装,比如:树形结构的菜单数据、部门数据等等。最近工作中,指标的树形结构封装场景频繁,比如:校验每个层级的指标权重之和要等于100,指标的满树校验等,接下来我们就来看一下我的思路。

    一、准备数据

    (1)准备一个指标实体类 

    @Data
    public class Indicator {
    
        private String code;
    
        private String parentCode;
    
        private String name;
    
        private Integer weight;
    
        private List<Indicator> children = Lists.newArrayList();
    
        public Indicator() {
        }
    
        public Indicator(String code, String parentCode, String name, Integer weight) {
            this.code = code;
            this.parentCode = parentCode;
            this.name = name;
            this.weight = weight;
        }
    }
    指标实体类

    (2)树形结构封装代码【并提供几个重载方法】

    import com.beust.jcommander.internal.Lists;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections4.CollectionUtils;
    
    import javax.annotation.Nonnull;
    import java.lang.reflect.Field;
    import java.util.*;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    
    /**
     * @author niubi
     */
    @Slf4j
    public class TreeUtil {
    
        /**
         * 组装树结构
         *
         * @param list                    .
         * @param fieldNameForIndex       主键名称
         * @param fieldNameForParentIndex parent主键名称
         * @param fieldNameForChildren    children名称
         * @return .
         */
        @SuppressWarnings(value = {"rawtypes", "unchecked"})
        public static Optional<List> treeify(@Nonnull List list, @Nonnull String fieldNameForIndex,
                                             @Nonnull String fieldNameForParentIndex, @Nonnull String fieldNameForChildren) {
            Objects.requireNonNull(list, "list is null");
            Objects.requireNonNull(fieldNameForIndex, "fieldNameForIndex is null");
            Objects.requireNonNull(fieldNameForChildren, "fieldNameForChildren is null");
            Objects.requireNonNull(fieldNameForParentIndex, "fieldNameForParentIndex is null");
            List root = new ArrayList();
    
            Map map = new HashMap();
            try {
                for (Object o : list) {
                    Field indexField = o.getClass().getDeclaredField(fieldNameForIndex);
                    if (!indexField.isAccessible()) {
                        indexField.setAccessible(true);
                    }
                    Object index = indexField.get(o);
                    map.put(index, o);
                }
                for (Object o : map.keySet()) {
                    Object obj = map.get(o);
                    Class<?> clazz = obj.getClass();
                    Field parentIndexField = clazz.getDeclaredField(fieldNameForParentIndex);
                    if (!parentIndexField.isAccessible()) {
                        parentIndexField.setAccessible(true);
                    }
                    Object parentIndex = parentIndexField.get(obj);
                    Object parent = map.get(parentIndex);
                    if (Objects.isNull(parent)) {
                        root.add(obj);
                        continue;
                    }
                    Class<?> clazz1 = parent.getClass();
                    Field childrenField = null;
                    try {
                        childrenField = clazz1.getDeclaredField(fieldNameForChildren);
                    } catch (Exception e) {
                        childrenField = clazz1.getSuperclass().getDeclaredField(fieldNameForChildren);
                    }
                    if (!childrenField.isAccessible()) {
                        childrenField.setAccessible(true);
                    }
    
                    List children = (List) childrenField.get(parent);
                    if (Objects.isNull(children)) {
                        children = new ArrayList();
                    }
    
                    children.add(obj);
                    childrenField.set(parent, children);
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                root = null;
            }
            return Optional.ofNullable(root);
        }
    
        @SuppressWarnings(value = {"rawtypes"})
        public static Optional<List> treeify(@Nonnull List list) {
            return treeify(list, "id", "parentId", "children");
        }
    
        @SuppressWarnings(value = {"rawtypes"})
        public static Optional<List> treeify(@Nonnull List list, @Nonnull String fieldNameForIndex) {
            return treeify(list, fieldNameForIndex, "parentId", "children");
        }
    
        @SuppressWarnings(value = {"rawtypes"})
        public static Optional<List> treeify(@Nonnull List list, @Nonnull String fieldNameForIndex, @Nonnull String fieldNameForParentIndex) {
            return treeify(list, fieldNameForIndex, fieldNameForParentIndex, "children");
        }
    }
    树形结构封装类

    (3)指标树形结构代码执行

    public static void testTree1() {
            List<Indicator> indicators = Lists.newArrayList();
            indicators.add(new Indicator("1", "0", "一级指标1", 50));
            indicators.add(new Indicator("11", "1", "二级指标1", 100));
            indicators.add(new Indicator("111", "11", "三级指标1", 100));
            indicators.add(new Indicator("2", "0", "一级指标2", 50));
            indicators.add(new Indicator("21", "2", "二级指标1", 50));
            indicators.add(new Indicator("221", "2", "二级指标2", 50));
    
            // 封装树形结构
            Optional<List> treeify = TreeUtil.treeify(indicators, "code", "parentCode", "children");
            List<Indicator> indicatorTree = Lists.newArrayList();
            if (treeify.isPresent()) {
                indicatorTree = treeify.get();
            }
            System.out.println("指标树形结构:" + indicatorTree);
        }
    指标树形结构代码执行

    二、校验指标树形结构中任意节点下的子节点权重之和为 100

    (1)方法一:使用递归方式进行逐层校验

    public static void testTree3() {
            List<Indicator> indicators = Lists.newArrayList();
            indicators.add(new Indicator("1", "0", "一级指标1", 50));
            indicators.add(new Indicator("11", "1", "二级指标1", 100));
            indicators.add(new Indicator("111", "11", "三级指标1", 100));
            indicators.add(new Indicator("2", "0", "一级指标2", 50));
            indicators.add(new Indicator("21", "2", "二级指标1", 50));
            indicators.add(new Indicator("221", "2", "二级指标2", 50));
    
            List<Indicator> oneIndicators = indicators.stream().filter(user -> user.getParentCode().equals("0")).collect(Collectors.toList());
            Integer sum = oneIndicators.stream().map(Indicator::getWeight).reduce((x, y) -> x + y).get();
            if (sum != 100) {
                System.out.println("一级指标权重之和校验失败");
                return;
            }
            // 逐个一级指标递归校验权重之和为 100
            for (Indicator user : oneIndicators) {
                Boolean flag = validData(user, indicators);
                if (!flag) {
                    System.out.println("校验失败");
                    return;
                }
            }
            System.out.println("校验成功");
        }
    
        private static Boolean validData(Indicator indicator, List<Indicator> indicators) {
            // 过滤出当前指标的下级指标集合
            List<Indicator> children = indicators.stream().filter(element -> element.getParentCode().equals(indicator.getCode())).collect(Collectors.toList());
            if (CollectionUtils.isEmpty(children)) {
                return true;
            }
            Integer sum = children.stream().map(Indicator::getWeight).reduce((x, y) -> x + y).get();
            if (sum != 100) {
                System.out.println("code = " + indicator.getCode() + ", parentCode = " + indicator.getParentCode() + ", 校验失败");
                return false;
            }
            for (Indicator child : children) {
                validData(child, indicators);
            }
            return true;
        }
    递归校验

    (2)方法二:使用非递归方式逐层校验

    public static void testTree2() {
            List<Indicator> indicators = Lists.newArrayList();
            indicators.add(new Indicator("1", "0", "一级指标1", 50));
            indicators.add(new Indicator("11", "1", "二级指标1", 100));
            indicators.add(new Indicator("111", "11", "三级指标1", 100));
            indicators.add(new Indicator("2", "0", "一级指标2", 50));
            indicators.add(new Indicator("21", "2", "二级指标1", 50));
            indicators.add(new Indicator("221", "2", "二级指标2", 50));
    
            // 封装树形结构
            Optional<List> treeify = TreeUtil.treeify(indicators, "code", "parentCode", "children");
            List<Indicator> userTree = Lists.newArrayList();
            if (treeify.isPresent()) {
                userTree = treeify.get();
            }
    
            // 判断每一个节点下所有子节点的分数累加之和是否是100
            Boolean flag = validAgeSum(userTree);
            if (flag) {
                System.out.println("校验成功");
            } else {
                System.out.println("校验失败");
            }
        }
    
        private static Boolean validAgeSum(List<Indicator> userTree) {
            List<Indicator> children = userTree;
            // 顶层一级指标校验
            Integer sum1  = children.stream().map(Indicator::getWeight).reduce((x, y) -> x + y).get();
            if (sum1 != 100) {
                return false;
            }
            // 非递归逐层每个根节点校验
            while(CollectionUtils.isNotEmpty(children)) {
                for (Indicator indicator : children) {
                    if (CollectionUtils.isEmpty(indicator.getChildren())) {
                        return true;
                    }
                    Integer sum2 = indicator.getChildren().stream().map(Indicator::getWeight).reduce((x, y) -> x + y).get();
                    if (sum2 != 100) {
                        System.out.println("code = " + indicator.getCode() + ", parentCode = " + indicator.getParentCode() + ", 校验失败");
                        return false;
                    }
                }
                // 重新组装子节点集合
                children = children.stream().map(Indicator::getChildren).flatMap(Collection::stream).collect(Collectors.toList());
            }
            return true;
        }
    非递归校验

    三、校验指标树形结构是否是一棵满树【满树:以任意一个一级指标节点开始,树的高度等同】

    /**
         * 封装树形结构
         */
        public static void testTree1() {
            List<Indicator> indicators = Lists.newArrayList();
            indicators.add(new Indicator("1", "0", "一级指标1", 50));
            indicators.add(new Indicator("11", "1", "二级指标1", 100));
            indicators.add(new Indicator("111", "11", "三级指标1", 100));
            indicators.add(new Indicator("2", "0", "一级指标2", 50));
            indicators.add(new Indicator("21", "2", "二级指标1", 50));
            indicators.add(new Indicator("211", "21", "san级指标1", 100));
            indicators.add(new Indicator("221", "2", "二级指标2", 50));
            indicators.add(new Indicator("222", "221", "三级指标1", 100));
    
            // 封装树形结构
            Optional<List> treeify = TreeUtil.treeify(indicators, "code", "parentCode", "children");
            List<Indicator> indicatorTree = Lists.newArrayList();
            if (treeify.isPresent()) {
                indicatorTree = treeify.get();
            }
            System.out.println("指标树形结构:" + indicatorTree);
    
            // 判断树是否是等高
            Boolean isFullTree = isFullTree(indicatorTree);
            System.out.println(isFullTree);
        }
    
        private static Boolean isFullTree(List<Indicator> userTree) {
            List<Indicator> children = userTree;
            while(CollectionUtils.isNotEmpty(children)) {
                // 获取所有根节点下的子节点列表
                List<List<Indicator>> lists = children.stream()
                        .map(Indicator::getChildren)
                        .map(userList -> Objects.isNull(userList) ? new ArrayList<Indicator>() : userList)
                        .collect(Collectors.toList());
                // 获取所有根节点下的子节点非空数量
                long count = lists.stream().filter(CollectionUtils::isNotEmpty).count();
                if (count != 0 && count < children.size()) {    // 去除特殊情况:最后一层叶子节点【count = 0】
                    return false;
                }
                children = lists.stream().flatMap(Collection::stream).collect(Collectors.toList());
            }
            return true;
        }
    满树校验

      推荐:hutool 工具类也是一款非常值得开发者去研究和借鉴,树形结构的封装也是很完美,而且还可以自行扩展字段信息;

      1)hutool 工具类官方文档

      2)hutool 中树结构封装文档

  • 相关阅读:
    Cider扩展架构:AttributeTable和AttributeTableBuilder
    VS编译时自动引用Debug|Release版本的dll
    扩展Oozie
    使用正则表达式找出不包含特定字符串的条目
    重构时机和重构方法之间的对应关系
    Javascript 果然存在所谓的预编译,例证:
    程序员眼中的UML(2)克服用例图的恐惧
    Hortonworks宣布一款Hadoop数据平台
    .NET Framework 4现已支持LocalDB
    REST API用得也痛苦
  • 原文地址:https://www.cnblogs.com/blogtech/p/14129014.html
Copyright © 2011-2022 走看看