【问题标题】:TreeView - How to count all children (including collapsed)TreeView - 如何计算所有孩子(包括折叠)
【发布时间】:2023-03-09 23:30:01
【问题描述】:

有没有一种方法可以获取 TreeView 对象中子项的数量?我想把所有的孩子,包括孩子的孩子,一直往下数。

getExpandedItemCount() 方法仅获取展开的子节点的子节点计数。有没有办法计算所有孩子的数量,无论它们是否被扩展。

【问题讨论】:

    标签: java javafx treeview


    【解决方案1】:

    这个答案中的解决方案对于只计算一棵小树中的节点来说太过分了。

    其他答案中的简单递归计数解决方案很好。提供此答案只是为了添加更多上下文和替代实现。

    堆栈与递归

    当您使用递归时,您隐含地依赖 Java 运行时为您维护一堆项目。对于非常大的树,这可能是一个问题,因为运行时可能会耗尽堆栈空间(堆栈溢出)。

    有关优先使用堆栈而不是递归的更多信息,请参阅:

    当然,如果你知道你正在处理的树很小,那么使用递归是可以的。有时递归算法比非递归算法更容易理解。

    基于迭代器的解决方案

    private class TreeIterator<T> implements Iterator<TreeItem<T>> {
        private Stack<TreeItem<T>> stack = new Stack<>();
    
        public TreeIterator(TreeItem<T> root) {
            stack.push(root);
        }
    
        @Override
        public boolean hasNext() {
            return !stack.isEmpty();
        }
    
        @Override
        public TreeItem<T> next() {
            TreeItem<T> nextItem = stack.pop();
            nextItem.getChildren().forEach(stack::push);
    
            return nextItem;
        }
    }
    

    使用迭代器计算树中项目的示例。

    TreeIterator<String> iterator = new TreeIterator<>(rootItem);
    int nItems = 0;
    while (iterator.hasNext()) {
        nItems++;
        iterator.next();
    }
    

    如果需要,iterator can be adapted to a stream,通过创建自定义流支持类,它允许您编写功能代码,例如:

    TreeItemStreamSupport.stream(rootItem)
        .filter(TreeItem::isExpanded)
        .count()
    

    示例程序

    import javafx.application.Application;
    import javafx.geometry.Insets;
    import javafx.scene.Scene;
    import javafx.scene.control.*;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    
    import java.util.*;
    import java.util.stream.*;
    
    public class TreeViewSample extends Application {
    
        // limits on randomly generated tree size.
        private static final int MAX_DEPTH = 8;
        private static final int MAX_CHILDREN_PER_NODE = 6;
        private static final double EXPANSION_PROPABILITY = 0.2;
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage stage) {
            Label numItemsLabel = new Label();
    
            // create a tree.
            TreeItem<String> rootItem = TreeFactory.createTree(
                    MAX_DEPTH,
                    MAX_CHILDREN_PER_NODE,
                    EXPANSION_PROPABILITY
            );
            rootItem.setExpanded(true);
            TreeView<String> tree = new TreeView<>(rootItem);
    
            numItemsLabel.setText(
                "Num Items: " + countExpandedItemsUsingStream(rootItem)
            );
    
            // display the number of items and the tree.
            VBox layout = new VBox(10, numItemsLabel, tree);
            layout.setPadding(new Insets(10));
    
            stage.setScene(new Scene(layout, 300, 250));
            stage.show();
        }
    
        // unused method demonstrating alternate solution.
        private long countItemsUsingIterator(TreeItem<String> rootItem) {
            TreeItemIterator<String> iterator = new TreeItemIterator<>(rootItem);
    
            int nItems = 0;
            while (iterator.hasNext()) {
                nItems++;
                iterator.next();
            }
    
            return nItems;
        }
    
        private long countExpandedItemsUsingStream(TreeItem<String> rootItem) {
            return
                    TreeItemStreamSupport.stream(rootItem)
                            .filter(TreeItem::isExpanded)
                            .count();
        }
    
        // unused method demonstrating alternate Jens-Peter Haack solution.
        private long countItemsUsingRecursion(TreeItem<?> node) {
            int count = 1;
    
            for (TreeItem child : node.getChildren()) {
                count += countItemsUsingRecursion(child);
            }
    
            return count;
        }
    
        /**
         * Random Tree generation algorithm.
         */
        private static class TreeFactory {
            private static final Random random = new Random(42);
    
            static TreeItem<String> createTree(
                    int maxDepth,
                    int maxChildrenPerNode,
                    double expansionProbability
            ) {
                TreeItem<String> root = new TreeItem<>("Root 0:0");
                Stack<DepthTreeItem> itemStack = new Stack<>();
                itemStack.push(new DepthTreeItem(root, 0));
    
                while (!itemStack.isEmpty()) {
                    int numChildren = random.nextInt(maxChildrenPerNode + 1);
    
                    DepthTreeItem nextItem = itemStack.pop();
                    int childDepth = nextItem.depth + 1;
    
                    for (int i = 0; i < numChildren; i++) {
                        TreeItem<String> child = new TreeItem<>(
                            "Item " + childDepth + ":" + i
                        );
                        child.setExpanded(random.nextDouble() < expansionProbability);
                        nextItem.treeItem.getChildren().add(child);
                        if (childDepth < maxDepth) {
                            itemStack.push(new DepthTreeItem(child, childDepth));
                        }
                    }
                }
    
                return root;
            }
    
            static class DepthTreeItem {
                DepthTreeItem(TreeItem<String> treeItem, int depth) {
                    this.treeItem = treeItem;
                    this.depth = depth;
                }
                TreeItem<String> treeItem;
                int depth;
            }
        }
    }
    
    /**
     * Provide a stream of tree items from a root tree item.
     */
    class TreeItemStreamSupport {
        public static <T> Stream<TreeItem<T>> stream(TreeItem<T> rootItem) {
            return asStream(new TreeItemIterator<>(rootItem));
        }
    
        private static <T> Stream<TreeItem<T>> asStream(TreeItemIterator<T> iterator) {
            Iterable<TreeItem<T>> iterable = () -> iterator;
    
            return StreamSupport.stream(
                    iterable.spliterator(),
                    false
            );
        }
    }
    
    /**
     * Iterate over items in a tree.
     * The tree should not be modified while this iterator is being used.
     *
     * @param <T> the type of items stored in the tree.
     */
    class TreeItemIterator<T> implements Iterator<TreeItem<T>> {
        private Stack<TreeItem<T>> stack = new Stack<>();
    
        public TreeItemIterator(TreeItem<T> root) {
            stack.push(root);
        }
    
        @Override
        public boolean hasNext() {
            return !stack.isEmpty();
        }
    
        @Override
        public TreeItem<T> next() {
            TreeItem<T> nextItem = stack.pop();
            nextItem.getChildren().forEach(stack::push);
    
            return nextItem;
        }
    }
    

    【讨论】:

    • 感谢您提供此插图。我只想指出,就目前情况而言,最后一个孩子最终会排在堆栈的顶部。我建议在调用forEach 之前将列表从getChildren() 颠倒过来。请注意,作为 Groovyist,我只需转到 reverse() 即可完成此操作,但必须有一个不错的 Java 方式。
    【解决方案2】:

    有一个很好的理由不提供一种方法来计算树的所有子节点,因为扩展后的树大小可能非常大甚至无限。

    例如:可以显示“真实”数字的所有数字的树:

    static class InfiniteNumberItem extends TreeItem<String> {
        boolean expanded = false;
        public InfiniteNumberItem(String name) {
            super(name);
        }
    
        @Override public ObservableList<TreeItem<String>> getChildren() {
            if (!expanded) {
                for (int i = 0; i < 10; i++)  {
                    super.getChildren().add(new InfiniteNumberItem(""+i));
                }
                expanded = true;
            }
            return super.getChildren();
        }
        @Override public boolean isLeaf() {
            return false;
        }
    }
    
    void testTreeInfinite(VBox box) { 
        TreeView<String> tree = new TreeView<String>();
        tree.prefHeightProperty().bind(box.heightProperty());
    
        tree.setRoot(new InfiniteNumberItem("3."));
        box.getChildren().add(tree);
    }
    

    但是,如果您知道自己在做什么,并且如果树的(扩展)大小受到限制,则您必须自己数数:

    int count(TreeItem<?> node) {
        int count = 1;
        for (TreeItem child : node.getChildren()) {
            count += count(child);
        }
        return count;
    }
    

    【讨论】:

      【解决方案3】:

      使用递归,类似这样:

      private static <T> long countChildren(TreeItem<T> treeItem) {
          long count = 0;
      
          if (treeItem != null) {
              ObservableList<TreeItem<T>> children = treeItem.getChildren();
      
              if (children != null) {
                  count += children.size();
      
                  for (TreeItem<T> child : children) {
                      count += countChildren(child);
                  }
              }
          }
      
          return count;
      }
      

      要在计数中包含根,请添加 1:

      long count = countChildren(treeItem) + 1;
      

      然后只需调用将根作为参数的方法:

      System.out.println(countChildren(treeView.getRoot()));
      

      【讨论】:

        猜你喜欢
        • 2022-08-19
        • 2019-08-04
        • 2016-11-25
        • 2011-03-12
        • 2021-02-11
        • 2019-12-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多