diff --git a/README.md b/README.md index 803b5cf..ae69df3 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,19 @@ conflict resolution rules: 1) longer matches prevail over shorter matches, 2) le There is only one result now: * "hot chocolate" starting at position 0, ending at position 12 +If you want the algorithm to only check for whole words, you can tell the Trie to do so: + +```java + Trie trie = new Trie().onlyWholeWords(); + trie.addKeyword("sugar"); + Collection emits = trie.parseText("sugarcane sugarcane sugar canesugar"); +``` + +In this case, it will only find one match, whereas it would normally find four. The sugarcane/canesugar words +are discarded because they are partial matches. + + + License ------- Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/main/java/org/ahocorasick/trie/Trie.java b/src/main/java/org/ahocorasick/trie/Trie.java index 769402a..7133a4e 100644 --- a/src/main/java/org/ahocorasick/trie/Trie.java +++ b/src/main/java/org/ahocorasick/trie/Trie.java @@ -36,6 +36,11 @@ public class Trie { return this; } + public Trie onlyWholeWords() { + this.trieConfig.setOnlyWholeWords(true); + return this; + } + public void addKeyword(String keyword) { State currentState = this.rootState; @@ -58,6 +63,10 @@ public class Trie { position++; } + if (trieConfig.isOnlyWholeWords()) { + removePartialMatches(text, collectedEmits); + } + if (!trieConfig.isAllowOverlaps()) { IntervalTree intervalTree = new IntervalTree((List)(List)collectedEmits); intervalTree.removeOverlaps((List) (List) collectedEmits); @@ -66,6 +75,24 @@ public class Trie { return collectedEmits; } + private void removePartialMatches(String searchText, List collectedEmits) { + long size = searchText.length(); + List removeEmits = new ArrayList(); + for (Emit emit : collectedEmits) { + if ((emit.getStart() == 0 || + searchText.charAt(emit.getStart() - 1) == ' ') && + (emit.getEnd() == size || + searchText.charAt(emit.getEnd() + 1) == ' ')) { + continue; + } + removeEmits.add(emit); + } + + for (Emit removeEmit : removeEmits) { + collectedEmits.remove(removeEmit); + } + } + private State getState(State currentState, Character character) { State newCurrentState = currentState.nextState(character); while (newCurrentState == null) { diff --git a/src/main/java/org/ahocorasick/trie/TrieConfig.java b/src/main/java/org/ahocorasick/trie/TrieConfig.java index 23bed1f..7dcfd0a 100644 --- a/src/main/java/org/ahocorasick/trie/TrieConfig.java +++ b/src/main/java/org/ahocorasick/trie/TrieConfig.java @@ -4,6 +4,8 @@ public class TrieConfig { private boolean allowOverlaps = true; + private boolean onlyWholeWords = false; + public boolean isAllowOverlaps() { return allowOverlaps; } @@ -12,4 +14,12 @@ public class TrieConfig { this.allowOverlaps = allowOverlaps; } + public boolean isOnlyWholeWords() { + return onlyWholeWords; + } + + public void setOnlyWholeWords(boolean onlyWholeWords) { + this.onlyWholeWords = onlyWholeWords; + } + } diff --git a/src/test/java/org/ahocorasick/trie/TrieTest.java b/src/test/java/org/ahocorasick/trie/TrieTest.java index 5802544..f569a5b 100644 --- a/src/test/java/org/ahocorasick/trie/TrieTest.java +++ b/src/test/java/org/ahocorasick/trie/TrieTest.java @@ -123,12 +123,18 @@ public class TrieTest { trie.addKeyword("n"); trie.addKeyword("urning"); Collection emits = trie.parseText("Turning"); - for (Emit emit : emits) { - System.out.println(emit.getStart()+":"+emit.getEnd()+"="+emit.getKeyword()); - } assertEquals(2, emits.size()); } + @Test + public void partialMatch() { + Trie trie = new Trie().onlyWholeWords(); + trie.addKeyword("sugar"); + Collection emits = trie.parseText("sugarcane sugarcane sugar canesugar"); // left, middle, right test + assertEquals(1, emits.size()); // Match must not be made + checkEmit(emits.iterator().next(), 20, 24, "sugar"); + } + private void checkEmit(Emit next, int expectedStart, int expectedEnd, String expectedKeyword) { assertEquals(expectedStart, next.getStart()); assertEquals(expectedEnd, next.getEnd());