com.google.api.generator.util.Trie Maven / Gradle / Ivy
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.api.generator.util;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* A common-prefix trie. T represents the type of each "char" in a word (which is a T-typed list).
*/
public class Trie {
private static class Node {
final U chr;
// Maintain insertion order to enable deterministic test output.
Map> children = new LinkedHashMap<>();
boolean isLeaf;
Node() {
chr = null;
}
Node(U chr) {
this.chr = chr;
}
}
private final Node root;
public Trie() {
root = new Node<>();
}
public void insert(List word) {
Map> children = root.children;
for (int i = 0; i < word.size(); i++) {
T chr = word.get(i);
Node t;
if (children.containsKey(chr)) {
t = children.get(chr);
} else {
t = new Node<>(chr);
children.put(chr, t);
}
children = t.children;
if (i == word.size() - 1) {
t.isLeaf = true;
}
}
}
/** Returns true if the word is in the trie. */
public boolean search(List word) {
Node node = searchNode(word);
return node != null && node.isLeaf;
}
/** Returns true if some word in the trie begins with the given prefix. */
public boolean hasPrefix(List prefix) {
return searchNode(prefix) != null;
}
/**
* Reduces the trie to a single value, via a DFS traversal.
*
* @param parentPreprocFn Transforms a parent node into an R-typed base value for consumption by
* the child nodes. The rest of the children will compute their values using this as a base as
* well, so it accumulates computational results as the traversal progresses. Does not handle
* the root node (i.e. when {@code chr} is null).
* @param leafReduceFn Transforms a child node into an R-typed value using the value computed by
* the parent nodes' preprocessing functions.
* @param parentPostprocFn Transforms the post-traversal result (from the child nodes) into
* R-typed values, further building upon {@code baseValue}. Must handle the root node, i.e.
* when {@code chr} is null.
* @param baseValue The base value upon which subsequent reductions will be performed. Ensure this
* is a type that can accumulate values, such as StringBuilder. An immutable type such as
* String will not work here.
*/
public R dfsTraverseAndReduce(
Function parentPreprocFn,
TriFunction parentPostprocFn,
BiFunction leafReduceFn,
R baseValue) {
return dfsTraverseAndReduce(root, parentPreprocFn, parentPostprocFn, leafReduceFn, baseValue);
}
/** Traverses the trie DFS-style, reducing all values into a single one on {@code baseValue}. */
private R dfsTraverseAndReduce(
Node node,
Function parentPreprocFn,
TriFunction parentPostprocFn,
BiFunction leafReduceFn,
R baseValue) {
if (node.isLeaf) {
return leafReduceFn.apply(node.chr, baseValue);
}
R leafReducedValue = node.chr == null ? baseValue : parentPreprocFn.apply(node.chr);
for (Map.Entry> e : node.children.entrySet()) {
// Thread the parent value through each of the children, and accumulate it.
leafReducedValue =
dfsTraverseAndReduce(
e.getValue(), parentPreprocFn, parentPostprocFn, leafReduceFn, leafReducedValue);
}
return parentPostprocFn.apply(node.chr, baseValue, leafReducedValue);
}
private Node searchNode(List word) {
Map> children = root.children;
Node t = null;
for (T chr : word) {
if (children.containsKey(chr)) {
t = children.get(chr);
children = t.children;
} else {
return null;
}
}
return t;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy