[tree] Add a Generalised Tree

This commit is contained in:
Paul Campbell 2018-09-30 17:36:29 +01:00
parent 073df36e56
commit f5c10e668d
5 changed files with 394 additions and 17 deletions

View file

@ -20,8 +20,14 @@
[[https://app.codacy.com/project/kemitix/mon/dashboard][file:https://img.shields.io/codacy/grade/d57096b0639d496aba9a7e43e7cf5b4c.svg?style=for-the-badge]] [[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]] [[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 #+BEGIN_SRC xml
<dependency> <dependency>
@ -798,7 +804,6 @@
.thenWith(v -> () -> {throw new IOException();}); .thenWith(v -> () -> {throw new IOException();});
#+END_SRC #+END_SRC
**** =Result<Maybe<T>> maybe(Predicate<T> predicate)= **** =Result<Maybe<T>> maybe(Predicate<T> predicate)=
Wraps the value within the Result in a Maybe, either a Just if the Wraps the value within the Result in a Maybe, either a Just if the
@ -851,6 +856,75 @@
#+END_SRC #+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 <R> Tree<R> leaf(R item)=
Create a leaf containing the item. The leaf has no sub-trees.
#+BEGIN_SRC java
final Tree<String> tree = Tree.leaf("item");
#+END_SRC
**** =static<R> Tree<R> of(R item, Collection<Tree<R>> subtrees)=
Create a tree containing the item and sub-trees.
#+BEGIN_SRC java
final Tree<String> tree = Tree.of("item", Collections.singletonList(Tree.leaf("leaf"));
#+END_SRC
*** Instance Methods
**** =<R> Tree<R> map(Function<T, R> f)=
Applies the function to the item within the Tree and to all sub-trees,
returning a new Tree.
#+BEGIN_SRC java
final Tree<UUID> tree = ...;
final Tree<String> result = tree.map(UUID::toString);
#+END_SRC
**** =Optional<T> item()=
Returns the contents of the Tree node within an Optional.
#+BEGIN_SRC java
final Tree<Item> tree = ...;
final Optional<Item> 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<Item> tree = ...;
final int result = tree.count();
#+END_SRC
**** =List<Tree<T> subTrees()=
Returns a list of sub-trees within the tree.
#+BEGIN_SRC java
final Tree<Item> tree = ...;
final List<Tree<Item>> result = tree.subTrees();
#+END_SRC
** Either ** Either
Allows handling a value that can be one of two types, a left value/type or a Allows handling a value that can be one of two types, a left value/type or a

View file

@ -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.
*
* <p>Each node may contain between 0 and n objects.</p>
*
* @param <T> the type of the objects help in the tree
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
@EqualsAndHashCode
class GeneralisedTree<T> implements Tree<T> {
private final T item;
private final List<Tree<T>> 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<Tree<T>> 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 <R> the type of object held in the resulting tree
* @return a tree
*/
@Override
public <R> Tree<R> map(final Function<T, R> f) {
return Tree.of(f.apply(item), mapSubTrees(f));
}
@Override
public Optional<T> item() {
return Optional.ofNullable(item);
}
private <R> List<Tree<R>> mapSubTrees(final Function<T, R> 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<Tree<T>> subTrees() {
return subTrees;
}
}

View file

@ -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 <T> the type of the objects help in the tree
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
public interface Tree<T> extends Functor<T, Tree<?>> {
/**
* Create a new generalised tree to hold object of a type.
*
* @param item the item for the leaf node
* @param <R> the type of the object to be held in the tree
*
* @return a empty generalised tree
*/
public static <R> Tree<R> 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 <R> the type of the item
* @return a leaf node of a generalised tree
*/
public static <R> Tree<R> of(final R item, final Collection<Tree<R>> subtrees) {
return new GeneralisedTree<>(item, subtrees);
}
@Override
public abstract <R> Tree<R> map(Function<T, R> f);
/**
* Return the item within the node of the tree, if present.
*
* @return an Optional containing the item
*/
public abstract Optional<T> 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<Tree<T>> subTrees();
}

View file

@ -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;

View file

@ -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<String> leaf = Tree.leaf(null);
//then
assertThat(leaf.item()).isEmpty();
}
@Test
public void canCreateANonEmptyLeaf() {
//given
final String item = "item";
//when
final Tree<String> leaf = Tree.leaf(item);
//then
assertThat(leaf.item()).contains(item);
}
@Test
public void emptyLeafHasCountZero() {
//given
final Tree<Object> tree = Tree.leaf(null);
//when
final int count = tree.count();
//then
assertThat(count).isZero();
}
@Test
public void nonEmptyLeafHasCountOne() {
//given
final Tree<String> 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<String> 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<UUID> tree = Tree.of(
uid1,
asList(
Tree.leaf(uid2),
Tree.leaf(uid3)));
//when
final Tree<String> result = tree.map(UUID::toString);
//then
assertThat(result).isEqualTo(Tree.of(
sid1,
asList(
Tree.leaf(sid2),
Tree.leaf(sid3)))
);
}
}