From f5c10e668d3095ffaadccbd40dc2f63b39a937d9 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 30 Sep 2018 17:36:29 +0100 Subject: [PATCH] [tree] Add a Generalised Tree --- README.org | 108 +++++++++++++++--- .../net/kemitix/mon/tree/GeneralisedTree.java | 100 ++++++++++++++++ src/main/java/net/kemitix/mon/tree/Tree.java | 88 ++++++++++++++ .../net/kemitix/mon/tree/package-info.java | 28 +++++ .../kemitix/mon/tree/GeneralisedTreeTest.java | 87 ++++++++++++++ 5 files changed, 394 insertions(+), 17 deletions(-) create mode 100644 src/main/java/net/kemitix/mon/tree/GeneralisedTree.java create mode 100644 src/main/java/net/kemitix/mon/tree/Tree.java create mode 100644 src/main/java/net/kemitix/mon/tree/package-info.java create mode 100644 src/test/java/net/kemitix/mon/tree/GeneralisedTreeTest.java diff --git a/README.org b/README.org index d24484d..918137b 100644 --- a/README.org +++ b/README.org @@ -2,26 +2,32 @@ ** TypeAlias, Maybe and Result for Java. - [[https://oss.sonatype.org/content/repositories/releases/net/kemitix/mon][file:https://img.shields.io/nexus/r/https/oss.sonatype.org/net.kemitix/mon.svg?style=for-the-badge]] - [[https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22net.kemitix%22%20AND%20a%3A%22mon%22][file:https://img.shields.io/maven-central/v/net.kemitix/mon.svg?style=for-the-badge]] + [[https://oss.sonatype.org/content/repositories/releases/net/kemitix/mon][file:https://img.shields.io/nexus/r/https/oss.sonatype.org/net.kemitix/mon.svg?style=for-the-badge]] + [[https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22net.kemitix%22%20AND%20a%3A%22mon%22][file:https://img.shields.io/maven-central/v/net.kemitix/mon.svg?style=for-the-badge]] - [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://img.shields.io/sonar/https/sonarcloud.io/net.kemitix%3Amon/coverage.svg?style=for-the-badge#.svg]] - [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://img.shields.io/sonar/https/sonarcloud.io/net.kemitix%3Amon/tech_debt.svg?style=for-the-badge#.svg]] - [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=sqale_rating#.svg]] - [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=alert_status#.svg]] - [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=reliability_rating#.svg]] - [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=security_rating#.svg]] - [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=sqale_index#.svg]] - [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=vulnerabilities#.svg]] - [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=bugs#.svg]] - [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=code_smells#.svg]] - [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=ncloc#.svg]] + [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://img.shields.io/sonar/https/sonarcloud.io/net.kemitix%3Amon/coverage.svg?style=for-the-badge#.svg]] + [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://img.shields.io/sonar/https/sonarcloud.io/net.kemitix%3Amon/tech_debt.svg?style=for-the-badge#.svg]] + [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=sqale_rating#.svg]] + [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=alert_status#.svg]] + [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=reliability_rating#.svg]] + [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=security_rating#.svg]] + [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=sqale_index#.svg]] + [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=vulnerabilities#.svg]] + [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=bugs#.svg]] + [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=code_smells#.svg]] + [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=ncloc#.svg]] - [[https://app.codacy.com/project/kemitix/mon/dashboard][file:https://img.shields.io/codacy/grade/d57096b0639d496aba9a7e43e7cf5b4c.svg?style=for-the-badge]] - [[http://i.jpeek.org/net.kemitix/mon/index.html][file:http://i.jpeek.org/net.kemitix/mon/badge.svg]] + [[https://app.codacy.com/project/kemitix/mon/dashboard][file:https://img.shields.io/codacy/grade/d57096b0639d496aba9a7e43e7cf5b4c.svg?style=for-the-badge]] + [[http://i.jpeek.org/net.kemitix/mon/index.html][file:http://i.jpeek.org/net.kemitix/mon/badge.svg]] + - [Maven Usage] + - [TypeAlias] + - [Maybe] + - [Result] + - [Tree] + - [Either] -** Maven +** Maven Usage #+BEGIN_SRC xml @@ -798,7 +804,6 @@ .thenWith(v -> () -> {throw new IOException();}); #+END_SRC - **** =Result> maybe(Predicate predicate)= Wraps the value within the Result in a Maybe, either a Just if the @@ -851,6 +856,75 @@ #+END_SRC +** Tree + + A Generalised tree, where each node may or may not have an item, and may have + any number of sub-trees. Leaf nodes are Trees with zero sub-trees. + +*** Static Constructors + +**** =static Tree leaf(R item)= + + Create a leaf containing the item. The leaf has no sub-trees. + + #+BEGIN_SRC java + final Tree tree = Tree.leaf("item"); + #+END_SRC + + +**** =static Tree of(R item, Collection> subtrees)= + + Create a tree containing the item and sub-trees. + + #+BEGIN_SRC java + final Tree tree = Tree.of("item", Collections.singletonList(Tree.leaf("leaf")); + #+END_SRC + + +*** Instance Methods + +**** = Tree map(Function f)= + + Applies the function to the item within the Tree and to all sub-trees, + returning a new Tree. + + #+BEGIN_SRC java + final Tree tree = ...; + final Tree result = tree.map(UUID::toString); + #+END_SRC + + +**** =Optional item()= + + Returns the contents of the Tree node within an Optional. + + #+BEGIN_SRC java + final Tree tree = ...; + final Optional result = tree.item(); + #+END_SRC + + +**** =int count()= + + Returns the total number of items in the tree, including sub-trees. Null + items don't count. + + #+BEGIN_SRC java + final Tree tree = ...; + final int result = tree.count(); + #+END_SRC + + +**** =List subTrees()= + + Returns a list of sub-trees within the tree. + + #+BEGIN_SRC java + final Tree tree = ...; + final List> result = tree.subTrees(); + #+END_SRC + + ** Either Allows handling a value that can be one of two types, a left value/type or a diff --git a/src/main/java/net/kemitix/mon/tree/GeneralisedTree.java b/src/main/java/net/kemitix/mon/tree/GeneralisedTree.java new file mode 100644 index 0000000..5e4cb7a --- /dev/null +++ b/src/main/java/net/kemitix/mon/tree/GeneralisedTree.java @@ -0,0 +1,100 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 Paul Campbell + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package net.kemitix.mon.tree; + +import lombok.EqualsAndHashCode; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * A generic tree of trees and objects. + * + *

Each node may contain between 0 and n objects.

+ * + * @param the type of the objects help in the tree + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +@EqualsAndHashCode +class GeneralisedTree implements Tree { + + private final T item; + private final List> subTrees; + + /** + * Creates a new tree. + * + * @param item the item of this node + * @param subTrees the sub-trees under this node + */ + GeneralisedTree(final T item, final Collection> subTrees) { + this.item = item; + this.subTrees = Collections.unmodifiableList(new ArrayList<>(subTrees)); + } + + /** + * Maps the tree using the function onto a new tree. + * + * @param f the function to apply + * @param the type of object held in the resulting tree + * @return a tree + */ + @Override + public Tree map(final Function f) { + return Tree.of(f.apply(item), mapSubTrees(f)); + } + + @Override + public Optional item() { + return Optional.ofNullable(item); + } + + private List> mapSubTrees(final Function f) { + return subTrees.stream() + .map(subTree -> subTree.map(f::apply)) + .collect(Collectors.toList()); + } + + /** + * Counts the number of items in the subtree. + * + * @return the sum of the subtrees, plus 1 if there is an item in this node + */ + @Override + @SuppressWarnings("avoidinlineconditionals") + public int count() { + return (item != null ? 1 : 0) + + subTrees.stream().mapToInt(Tree::count).sum(); + } + + /** + * Returns a list of subtrees. + * + * @return a List of trees + */ + @Override + public List> subTrees() { + return subTrees; + } +} diff --git a/src/main/java/net/kemitix/mon/tree/Tree.java b/src/main/java/net/kemitix/mon/tree/Tree.java new file mode 100644 index 0000000..941461b --- /dev/null +++ b/src/main/java/net/kemitix/mon/tree/Tree.java @@ -0,0 +1,88 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 Paul Campbell + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package net.kemitix.mon.tree; + +import net.kemitix.mon.Functor; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +/** + * A tree of objects. + * + * @param the type of the objects help in the tree + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +public interface Tree extends Functor> { + + /** + * Create a new generalised tree to hold object of a type. + * + * @param item the item for the leaf node + * @param the type of the object to be held in the tree + * + * @return a empty generalised tree + */ + public static Tree leaf(final R item) { + return new GeneralisedTree<>(item, Collections.emptyList()); + } + + /** + * Create a new generalised tree to hold the single item. + * + * @param item the item for the tree node + * @param subtrees the subtrees of the tree node + * @param the type of the item + * @return a leaf node of a generalised tree + */ + public static Tree of(final R item, final Collection> subtrees) { + return new GeneralisedTree<>(item, subtrees); + } + + @Override + public abstract Tree map(Function f); + + /** + * Return the item within the node of the tree, if present. + * + * @return an Optional containing the item + */ + public abstract Optional item(); + + /** + * Count the number of item in the tree, including subtrees. + * + * @return the number of items + */ + public abstract int count(); + + /** + * The subtrees of the tree. + * + * @return a list of Trees + */ + public abstract List> subTrees(); +} diff --git a/src/main/java/net/kemitix/mon/tree/package-info.java b/src/main/java/net/kemitix/mon/tree/package-info.java new file mode 100644 index 0000000..f234a58 --- /dev/null +++ b/src/main/java/net/kemitix/mon/tree/package-info.java @@ -0,0 +1,28 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 Paul Campbell + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Tree data structures. + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ + +package net.kemitix.mon.tree; diff --git a/src/test/java/net/kemitix/mon/tree/GeneralisedTreeTest.java b/src/test/java/net/kemitix/mon/tree/GeneralisedTreeTest.java new file mode 100644 index 0000000..5771434 --- /dev/null +++ b/src/test/java/net/kemitix/mon/tree/GeneralisedTreeTest.java @@ -0,0 +1,87 @@ +package net.kemitix.mon.tree; + +import org.assertj.core.api.WithAssertions; +import org.junit.Test; + +import java.util.UUID; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; + +public class GeneralisedTreeTest implements WithAssertions { + + @Test + public void canCreateAnEmptyLeaf() { + //when + final Tree leaf = Tree.leaf(null); + //then + assertThat(leaf.item()).isEmpty(); + } + + @Test + public void canCreateANonEmptyLeaf() { + //given + final String item = "item"; + //when + final Tree leaf = Tree.leaf(item); + //then + assertThat(leaf.item()).contains(item); + } + + @Test + public void emptyLeafHasCountZero() { + //given + final Tree tree = Tree.leaf(null); + //when + final int count = tree.count(); + //then + assertThat(count).isZero(); + } + + @Test + public void nonEmptyLeafHasCountOne() { + //given + final Tree tree = Tree.leaf("value"); + //when + final int count = tree.count(); + //then + assertThat(count).isEqualTo(1); + } + + @Test + public void canCreateTreeWithSubTrees() { + //given + final String treeItem = "tree"; + final String leafItem = "leaf"; + //when + final Tree tree = Tree.of(treeItem, singletonList(Tree.leaf(leafItem))); + //then + assertThat(tree.subTrees()).containsExactly(Tree.leaf(leafItem)); + } + + @Test + public void canMapNestedTrees() { + //given + final UUID uid1 = UUID.randomUUID(); + final UUID uid2 = UUID.randomUUID(); + final UUID uid3 = UUID.randomUUID(); + final String sid1 = uid1.toString(); + final String sid2 = uid2.toString(); + final String sid3 = uid3.toString(); + final Tree tree = Tree.of( + uid1, + asList( + Tree.leaf(uid2), + Tree.leaf(uid3))); + //when + final Tree result = tree.map(UUID::toString); + //then + assertThat(result).isEqualTo(Tree.of( + sid1, + asList( + Tree.leaf(sid2), + Tree.leaf(sid3))) + ); + } + +} \ No newline at end of file