All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.aksw.commons.collections.trees.TreeUtils Maven / Gradle / Ivy

There is a newer version: 0.9.9
Show newest version
package org.aksw.commons.collections.trees;

import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.aksw.commons.collections.multimaps.MultimapUtils;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;

public class TreeUtils {

//	Predicate p = x -> !(x instanceof OpService);
    /**
     * Given a predicate, return the minimum set of nodes, for which all nodes in their subtree satisfy the predicate.
     * The algo starts with the set X of leaf nodes satisfying the predicate, and moves upwards.
     * If all children of a parent satisfy the predicate, the children are removed from X and the parent is added instead.
     *
     * Note: Uses IdentityHashSet
     *
     * @param tree
     * @param predicate
     * @return
     */
    public static  Set propagateBottomUpLabel(Tree tree, Predicate predicate) {
        Collection leafs = TreeUtils.getLeafs(tree);

        Set result = leafs.stream()
                .filter(predicate)
                .collect(Collectors.toCollection(Sets::newIdentityHashSet));

        for(;;) {
            //List parents = levels.get(i);
            Set parents = result.stream()
                    .map(tree::getParent)
                    .filter(x -> x != null)
                    .collect(Collectors.toCollection(Sets::newIdentityHashSet));

            boolean anyMatch = false;
            for(T parent : parents) {
                Collection children = tree.getChildren(parent);
                boolean allChildrenTagged = children.stream().allMatch(result::contains);

                if(allChildrenTagged) {
                    anyMatch = true;
                    result.removeAll(children);
                    result.add(parent);
                }
            }

            if(!anyMatch) {
                break;
            }
        }

        return result;
    }


    // TODO We may want to add a sub-tree function, which allows using any of a base-tree's node as the root
    public static  Tree subTree(Tree tree, T newRoot) {
        Tree result = new SubTree<>(tree, newRoot);
        return result;
    }

    public static  long nodeCount(Tree tree) {
        long result = Collections.singleton(tree.getRoot()).stream()
                .flatMap(x -> tree.getChildren(x).stream())
                .count();

        return result;

    }

    public static  long depth(Tree tree) {
        T root = tree.getRoot();
        long result = depth(tree, root);
        return result;

    }

    public static  long depth(Tree tree, T node) {
        long result;
        if(node == null) {
            result = 0;
        } else {
            Collection children = tree.getChildren(node);
            result = 1l + children.stream()
                    .mapToLong(child -> depth(tree, child))
                    .max()
                    .orElse(0l);
        }

        return result;
    }


    public static  int childIndexOf(TreeOps2 ops, T node) {
        List children = ops.getParentToChildren().apply(node);
        int result = children.indexOf(node);
        return result;
    }

    //  Replaces a node in the tree - returns a new tree object.
    public static  Tree replace(Tree tree, T node, T replacement) {
        // TODO Make the equivalence test configurable
        T newRoot = replaceNode(tree, node, replacement, (a, b) -> a == b);

        Tree result = tree.createNew(newRoot); //TreeImpl.create(newRoot, tree.getOps());
        return result;
    }

    public static  int indexOf(List list, T find, BiPredicate isEquiv) {
        Iterator it = list.iterator();
        int i = 0;
        boolean found = false;
        while(it.hasNext()) {
            T item = it.next();
            found = isEquiv.test(item, find);
            if(found) {
                break;
            }
        }

        int result = found ? i : -1;
        return result;
    }

    public static  T replaceNode(Tree tree, T node, T replacement, BiPredicate isEquiv) {
        T result;

        if(node == null) {
             result = replacement;
        } else {
            T parent = tree.getParent(node);
            List children = new ArrayList(tree.getChildren(parent));
            int i = indexOf(children, node, isEquiv);
            //int i = children.indexOf(node);


            children.set(i, replacement);
            T parentReplacement = tree.copy(parent, children);

            result = replaceNode(tree, parent, parentReplacement, isEquiv);
        }

        return result;
    }



    public static  T substitute(
            T node,
            boolean descendIntoSubst,
            TreeOps2 ops,
            Function transformFn
            )
    {
        T result = substitute(node, descendIntoSubst, ops.getParentToChildren(), transformFn, ops.getReplaceChildren());
        return result;
    }

    public static  T substitute(
            T node,
            boolean descendIntoSubst,
            Function> getChildrenFn,
            Function transformFn,
            BiFunction, T> copyFn)
    {
        T tmp = transformFn.apply(node);

        // Descend into op if tmp was null (assigned in statement after this)
        // or descend into the replacement op
        boolean descend = tmp == null || descendIntoSubst;

        // Use op if tmp is null
        tmp = tmp == null ? node : tmp;

        T result;
        if(descend) {
            List newSubOps = getChildrenFn.apply(tmp).stream()
                .map(subOp -> substitute(subOp, descendIntoSubst, getChildrenFn, transformFn, copyFn))
                .collect(Collectors.toList());

            result = copyFn.apply(node, newSubOps); //OpUtils.copy(op, newSubOps);
        } else {
            result = tmp;
        }

        return result;
    }


    /**
     * Find the first ancestor for which the predicate evaluates to true
     * @param tree
     * @param node
     * @param predicate
     *
     * @return
     */
    public static  T findAncestor(Tree tree, T node, java.util.function.Predicate predicate) {
        T current = node;
        do {
            current = tree.getParent(current);
        } while(current != null && !predicate.test(current));

        return current;
    }

    public static  Stream inOrderSearch(T node, Function> parentToChildren) {
        Stream result = Stream.of(node);

        Iterable children = parentToChildren.apply(node);
        Stream childStream = StreamSupport.stream(children.spliterator(), false);

        result = Stream.concat(
                result,
                childStream.flatMap(c -> inOrderSearch(
                        c,
                        parentToChildren
                )));

        return result;

    }

    /**
     * In-order-search starting from the given node and descending into the tree.
     * Each node may be mapped to a value.
     * A predicate determines whether to stop descending further into a sub-tree.
     * Useful for extracting patterns from a tree.
     *
     * @param node
     * @param parentToChildren
     * @param nodeToValue
     * @param doDescend
     * @return
     */
    public static  Stream> inOrderSearch(
            T node,
            Function> parentToChildren,
            Function nodeToValue,
            BiPredicate doDescend
            ) {

        V value = nodeToValue.apply(node);
        Entry e = new SimpleEntry<>(node, value);
        boolean descend = doDescend.test(node, value);

        Stream> result = Stream.of(e);
        if(descend) {
            Iterable children = parentToChildren.apply(node);
            Stream childStream = StreamSupport.stream(children.spliterator(), false);

            result = Stream.concat(
                    result,
                    childStream.flatMap(c -> inOrderSearch(
                            c,
                            parentToChildren,
                            nodeToValue,
                            doDescend)));
        }

        return result;
    }

    /**
     * Returns the set of nodes in each level of the tree
     * The set containing the root will be the first item in the list
     *
     *
     * @param tree
     * @return
     */
    public static  List> nodesPerLevel(Tree tree) {
        List> result = new ArrayList<>();

        //Set current = Collections.singleton(tree.getRoot());
        List current = Collections.singletonList(tree.getRoot());
        while(!current.isEmpty()) {
            result.add(current);
            //Set next = new LinkedHashSet<>();
            List next = new ArrayList<>();
            for(T node : current) {
                Collection children = tree.getChildren(node);
                next.addAll(children);
            }

            current = next;
        }

        return result;
    }

    public static  List getLeafs(Tree tree) {
        T root = tree.getRoot();
        List result = inOrderSearch(root, tree::getChildren)
            .filter(node -> node == null ? true : tree.getChildren(node).isEmpty())
            .collect(Collectors.toList());

//        List result = new ArrayList();
//        T root = tree.getRoot();
//        getLeafs(result, tree, root);
        return result;
    }

    public static  void getLeafs(Collection result, Tree tree, T node) {
        Collection children = tree.getChildren(node);
        if(children.isEmpty()) {
            result.add(node);
        } else {
            for(T child : children) {
                getLeafs(result, tree, child);
            }
        }
    }

    /**
     * Get the set of immediate parents for a given set of children
     *
     * @param tree
     * @param children
     * @return
     */
    public static  Set getParentsOf(Tree tree, Iterable children) {
        Set result = new HashSet();
        for(T child: children) {
            T parent = tree.getParent(child);
            result.add(parent);
        }

        return result;
    }

    /**
     * Traverse an op structure and create a map from each subOp to its immediate parent
     *
     * NOTE It must be ensured that common sub expressions are different objects,
     * since we are using an identity hash map for mapping children to parents
     *
     *
     * @param op
     * @return
     */
    public static  Map parentMap(T root, Function> parentToChildren) {
        Map result = new IdentityHashMap();

        result.put(root, null);

        parentMap(result, root, parentToChildren);
        return result;
    }


    public static  void parentMap(Map result, T parent, Function> parentToChildren) {
        List children = parentToChildren.apply(parent);

        for(T child : children) {
            result.put(child, parent);

            parentMap(result, child, parentToChildren);
        }
    }


    /**
     * Create a new tree object which has certain nodes remapped with *leaf* nodes
     *
     * @param tree
     * @param remapFn
     * @return
     */
    public static  Tree remapSubTreesToLeafs(Tree tree, Function remapFn) {
        Map fwd = TreeUtils
                .inOrderSearch(
                        tree.getRoot(),
                        tree::getChildren,
                        remapFn,
                        (opNode, value) -> value == null) // descend while the value is null
                .filter(e -> e.getValue() != null)
                .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> b, IdentityHashMap::new));

        Map bwd = new IdentityHashMap<>();
        for(Entry e : fwd.entrySet()) {
            bwd.put(e.getValue(), e.getKey());
        }

        Tree result = new TreeReplace<>(tree, fwd, bwd);
        return result;
    }

    public static  Tree removeUnaryNodes(Tree tree) {
        ListMultimap parentToChildren = MultimapUtils.newIdentityListMultimap(); //ArrayListMultimap.create();
        T newRoot = removeUnaryNodes(tree, tree.getRoot(), parentToChildren);

        Tree result = newRoot == null
                ? null
                : TreeImpl.create(newRoot, (node) -> parentToChildren.get(node));

        return result;
    }

    public static  T removeUnaryNodes(Tree tree, T node, ListMultimap parentToChildren) {
        Collection children = tree.getChildren(node);
        int childCount = children.size();

        T result;
        switch(childCount) {
        case 0:
            result = node;
            break;
        case 1:
            T child = children.iterator().next();
            result = removeUnaryNodes(tree, child, parentToChildren);
            break;
        default:
            result = node;
            for(T c : children) {
                T newChild = removeUnaryNodes(tree, c, parentToChildren);
                parentToChildren.put(node,  newChild);
            }
            break;
        }

        return result;
    }

    // TODO: Another output format: Map, Multimap>
    /**
     * Input: A mapping from cache nodes to candidate query nodes represented as a Multimap.
     * Output: The mapping partitioned by each node's first multiary ancestor.
     *
     * Output could also be: Multimap - fmaToNodesCache
     *
     *
     * For every cacheFma, map to the corresponding queryFmas - and for
     * each of these mappings yield the candidate node mappings of the children
     * Multimap>>
     *
     * Q: What if cache nodes do not have a fma?
     * A: In this case the fma would be null, which means that there can only be a single cache node
     * which would be grouped with a null fma.
     * In the query, we can then check whether we are pairing a union with another union or null.
     *
     * We always map from cache to query.
     *
     * Map
     *
     * So the challenge is now again how to represent all the facts and how to perform
     * the permutations / combinations...
     *
     *
     *
     *
     *
     * @param cacheTree
     * @param tree
     * @param cacheToQueryCands
     * @return
     */
    public static  Map> clusterNodesByFirstMultiaryAncestor(Tree tree, Multimap mapping) { //Collection nodes) {
        Map> result = new HashMap<>();

        Set>> entries = mapping.asMap().entrySet();
        for(Entry> entry : entries) {
            T node = entry.getKey();
            //T multiaryAncestor = firstMultiaryAncestor(tree, cacheNode);
            T multiaryAncestor = tree.getParent(node);
            Collection queryNodes = entry.getValue();

            for(T targetNode : queryNodes) {
                Multimap mm = result.computeIfAbsent(multiaryAncestor, (k) -> HashMultimap.create());
                mm.put(node, targetNode);
            }
        }

        return result;
    }

    /**
     * Return a node's first ancestor having an arity > 1
     * null if there is none.
     *
     * @param tree
     * @param node
     * @return
     */
    public static  T firstMultiaryAncestor(Tree tree, T node) {
        T result = null;
        T current = node;
        while(current != null) {
            T parent = tree.getParent(result);
            Collection children = tree.getChildren(parent);
            int arity = children.size();
            if(arity > 1) {
                result = parent;
                break;
            }
            current = parent;
        }
        return result;
    }

    public static  List getUnaryAncestors(X x, Tree tree, Tree multiaryTree) {
        List result = new ArrayList<>();

        X ancestor = multiaryTree.getParent(x);

        X currentNode = x;
        while((currentNode = tree.getParent(currentNode)) != null && !currentNode.equals(ancestor)) {
            result.add(currentNode);
        }


        return result;
    }

    // TODO How to handle root nodes / null values?
    /**
     * Given a mapping of child nodes, determine which parents may be mapped to each other.
     * For any nodes mapped in 'childMapping', their parents may be mapped as well.
     *
     *
     * @param aTree
     * @param bTree
     * @param childMapping
     * @return
     */
    public static  Multimap deriveParentMapping(Tree aTree, Tree bTree, Multimap childMapping) {
        Multimap result = HashMultimap.create();
        Set as = childMapping.keySet();
        for(A a : as) {
            A aParent = aTree.getParent(a);
            Collection bs = childMapping.get(a);
            Set bParents = getParentsOf(bTree, bs);

            result.putAll(aParent, bParents);
        }

        return result;
    }

}