package mhfc.net.common.util; import java.util.EnumMap; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Supplier; import com.google.common.base.Preconditions; public class Trie<H, T> { private static abstract class BasicNode<H, T> { public abstract Optional<T> findFirstPrefix(Iterator<H> input); public abstract BasicNode<H, T> insert(Iterator<H> input, T data); } private class DataNode extends BasicNode<H, T> { private T data; private Map<H, BasicNode<H, T>> continuationNodes; public DataNode() { this.data = null; this.continuationNodes = mapMaker.get(); } private BasicNode<H, T> getNodeFor(H input) { return continuationNodes.getOrDefault(input, END_NODE_INSTANCE); } private void putNodeFor(H input, BasicNode<H, T> newNode) { if (newNode == null || newNode == END_NODE_INSTANCE) { return; } continuationNodes.put(input, newNode); } @Override public Optional<T> findFirstPrefix(Iterator<H> input) { if (data != null) { return Optional.of(data); } if (!input.hasNext()) { return Optional.empty(); } return getNodeFor(input.next()).findFirstPrefix(input); } @Override public BasicNode<H, T> insert(Iterator<H> input, T data) { if (!input.hasNext()) { Preconditions.checkArgument(data != null, "data " + this.data + "already exists for input " + input); this.data = data; } else { H trialValue = input.next(); putNodeFor(trialValue, getNodeFor(trialValue).insert(input, data)); } return this; } } private class EndNode extends BasicNode<H, T> { @Override public Optional<T> findFirstPrefix(Iterator<H> input) { return Optional.empty(); } @Override public BasicNode<H, T> insert(Iterator<H> input, T data) { return new DataNode().insert(input, data); } } private EndNode END_NODE_INSTANCE = new EndNode(); private Supplier<Map<H, BasicNode<H, T>>> mapMaker; private BasicNode<H, T> topNode = END_NODE_INSTANCE; public Trie() { this(Trie::newHashMapForType); } public Trie(Class<H> clazzH) { this(selectMapTypeFor(clazzH)); } private Trie(Supplier<Map<H, BasicNode<H, T>>> mapMaker) { this.mapMaker = java.util.Objects.requireNonNull(mapMaker); } private static <U, V> Map<U, BasicNode<U, V>> newHashMapForType() { return new HashMap<>(); } @SuppressWarnings({ "unchecked", "rawtypes" }) private static <U, V> Supplier<Map<U, BasicNode<U, V>>> newEnumMapUnchecked(Class<U> enumClass) { assert enumClass.isEnum(); return () -> new EnumMap(enumClass); } private static <U, V> Supplier<Map<U, BasicNode<U, V>>> selectMapTypeFor(Class<U> clazzH) { if (clazzH != null && clazzH.isEnum()) { return newEnumMapUnchecked(clazzH); } return Trie::newHashMapForType; } /** * Inserts a possiblity into the trie. * * @param sequence * @param data */ public void insert(List<H> sequence, T data) { java.util.Objects.requireNonNull(data); this.topNode = topNode.insert(sequence.iterator(), data); } /** * Finds the first data T that is a prefix of muster * * @param muster * @return */ public Optional<T> findFirstPrefix(Iterable<H> muster) { return this.topNode.findFirstPrefix(muster.iterator()); } }