pull #14 implemented pull request by rripken for containsMatch and firstMatch

This commit is contained in:
robert-bor 2015-09-22 22:02:30 +02:00
parent 4633b1ba2a
commit e2c5334234
2 changed files with 74 additions and 66 deletions

View File

@ -85,11 +85,11 @@ public class Trie {
return collectedEmits; return collectedEmits;
} }
public boolean matches(String text) public boolean containsMatch(CharSequence text) {
{
Emit firstMatch = firstMatch(text); Emit firstMatch = firstMatch(text);
return firstMatch != null; return firstMatch != null;
} }
public void parseText(CharSequence text, EmitHandler emitHandler) { public void parseText(CharSequence text, EmitHandler emitHandler) {
State currentState = this.rootState; State currentState = this.rootState;
for (int position = 0; position < text.length(); position++) { for (int position = 0; position < text.length(); position++) {
@ -105,21 +105,7 @@ public class Trie {
} }
private void removePartialMatches(CharSequence searchText, List<Emit> collectedEmits) { public Emit firstMatch(CharSequence text) {
long size = searchText.length();
List<Emit> removeEmits = new ArrayList<Emit>();
for (Emit emit : collectedEmits) {
if ((emit.getStart() == 0 ||
!Character.isAlphabetic(searchText.charAt(emit.getStart() - 1))) &&
(emit.getEnd() + 1 == size ||
!Character.isAlphabetic(searchText.charAt(emit.getEnd() + 1)))) {
continue;
}
removeEmits.add(emit);
}
public Emit firstMatch(String text)
{
if (!trieConfig.isAllowOverlaps()) { if (!trieConfig.isAllowOverlaps()) {
// Slow path. Needs to find all the matches to detect overlaps. // Slow path. Needs to find all the matches to detect overlaps.
Collection<Emit> parseText = parseText(text); Collection<Emit> parseText = parseText(text);
@ -127,11 +113,10 @@ public class Trie {
return parseText.iterator().next(); return parseText.iterator().next();
} }
} else { } else {
// Fast path. Returs first match found. // Fast path. Returns first match found.
checkForConstructedFailureStates();
int position = 0;
State currentState = this.rootState; State currentState = this.rootState;
for (Character character : text.toCharArray()) { for (int position = 0; position < text.length(); position++) {
Character character = text.charAt(position);
if (trieConfig.isCaseInsensitive()) { if (trieConfig.isCaseInsensitive()) {
character = Character.toLowerCase(character); character = Character.toLowerCase(character);
} }
@ -149,23 +134,20 @@ public class Trie {
} }
} }
} }
position++;
} }
} }
return null; return null;
} }
private boolean isPartialMatch(String searchText, Emit emit) private boolean isPartialMatch(CharSequence searchText, Emit emit) {
{
return (emit.getStart() != 0 && return (emit.getStart() != 0 &&
Character.isAlphabetic(searchText.charAt(emit.getStart() - 1))) || Character.isAlphabetic(searchText.charAt(emit.getStart() - 1))) ||
(emit.getEnd() + 1 != searchText.length() && (emit.getEnd() + 1 != searchText.length() &&
Character.isAlphabetic(searchText.charAt(emit.getEnd() + 1))); Character.isAlphabetic(searchText.charAt(emit.getEnd() + 1)));
} }
private void removePartialMatches(String searchText, List<Emit> collectedEmits) private void removePartialMatches(CharSequence searchText, List<Emit> collectedEmits) {
{ List<Emit> removeEmits = new ArrayList<>();
List<Emit> removeEmits = new ArrayList<Emit>();
for (Emit emit : collectedEmits) { for (Emit emit : collectedEmits) {
if (isPartialMatch(searchText, emit)) { if (isPartialMatch(searchText, emit)) {
removeEmits.add(emit); removeEmits.add(emit);

View File

@ -9,6 +9,7 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class TrieTest { public class TrieTest {
@ -24,8 +25,9 @@ public class TrieTest {
@Test @Test
public void keywordAndTextAreTheSameFirstMatch() { public void keywordAndTextAreTheSameFirstMatch() {
Trie trie = new Trie(); Trie trie = Trie.builder()
trie.addKeyword("abc"); .addKeyword("abc")
.build();
Emit firstMatch = trie.firstMatch("abc"); Emit firstMatch = trie.firstMatch("abc");
checkEmit(firstMatch, 0, 2, "abc"); checkEmit(firstMatch, 0, 2, "abc");
} }
@ -42,8 +44,9 @@ public class TrieTest {
@Test @Test
public void textIsLongerThanKeywordFirstMatch() { public void textIsLongerThanKeywordFirstMatch() {
Trie trie = new Trie(); Trie trie = Trie.builder()
trie.addKeyword("abc"); .addKeyword("abc")
.build();
Emit firstMatch = trie.firstMatch(" abc"); Emit firstMatch = trie.firstMatch(" abc");
checkEmit(firstMatch, 1, 3, "abc"); checkEmit(firstMatch, 1, 3, "abc");
} }
@ -62,10 +65,11 @@ public class TrieTest {
@Test @Test
public void variousKeywordsFirstMatch() { public void variousKeywordsFirstMatch() {
Trie trie = new Trie(); Trie trie = Trie.builder()
trie.addKeyword("abc"); .addKeyword("abc")
trie.addKeyword("bcd"); .addKeyword("bcd")
trie.addKeyword("cde"); .addKeyword("cde")
.build();
Emit firstMatch = trie.firstMatch("bcd"); Emit firstMatch = trie.firstMatch("bcd");
checkEmit(firstMatch, 0, 2, "bcd"); checkEmit(firstMatch, 0, 2, "bcd");
} }
@ -104,11 +108,12 @@ public class TrieTest {
@Test @Test
public void ushersTestFirstMatch() { public void ushersTestFirstMatch() {
Trie trie = new Trie(); Trie trie = Trie.builder()
trie.addKeyword("hers"); .addKeyword("hers")
trie.addKeyword("his"); .addKeyword("his")
trie.addKeyword("she"); .addKeyword("she")
trie.addKeyword("he"); .addKeyword("he")
.build();
Emit firstMatch = trie.firstMatch("ushers"); Emit firstMatch = trie.firstMatch("ushers");
checkEmit(firstMatch, 2, 3, "he"); checkEmit(firstMatch, 2, 3, "he");
} }
@ -150,8 +155,9 @@ public class TrieTest {
@Test @Test
public void misleadingTestFirstMatch() { public void misleadingTestFirstMatch() {
Trie trie = new Trie(); Trie trie = Trie.builder()
trie.addKeyword("hers"); .addKeyword("hers")
.build();
Emit firstMatch = trie.firstMatch("h he her hers"); Emit firstMatch = trie.firstMatch("h he her hers");
checkEmit(firstMatch, 9, 12, "hers"); checkEmit(firstMatch, 9, 12, "hers");
} }
@ -174,11 +180,12 @@ public class TrieTest {
@Test @Test
public void recipesFirstMatch() { public void recipesFirstMatch() {
Trie trie = new Trie(); Trie trie = Trie.builder()
trie.addKeyword("veal"); .addKeyword("veal")
trie.addKeyword("cauliflower"); .addKeyword("cauliflower")
trie.addKeyword("broccoli"); .addKeyword("broccoli")
trie.addKeyword("tomatoes"); .addKeyword("tomatoes")
.build();
Emit firstMatch = trie.firstMatch("2 cauliflowers, 3 tomatoes, 4 slices of veal, 100g broccoli"); Emit firstMatch = trie.firstMatch("2 cauliflowers, 3 tomatoes, 4 slices of veal, 100g broccoli");
checkEmit(firstMatch, 2, 12, "cauliflower"); checkEmit(firstMatch, 2, 12, "cauliflower");
@ -186,9 +193,10 @@ public class TrieTest {
@Test @Test
public void longAndShortOverlappingMatch() { public void longAndShortOverlappingMatch() {
Trie trie = new Trie(); Trie trie = Trie.builder()
trie.addKeyword("he"); .addKeyword("he")
trie.addKeyword("hehehehe"); .addKeyword("hehehehe")
.build();
Collection<Emit> emits = trie.parseText("hehehehehe"); Collection<Emit> emits = trie.parseText("hehehehehe");
Iterator<Emit> iterator = emits.iterator(); Iterator<Emit> iterator = emits.iterator();
checkEmit(iterator.next(), 0, 1, "he"); checkEmit(iterator.next(), 0, 1, "he");
@ -215,17 +223,28 @@ public class TrieTest {
checkEmit(iterator.next(), 6, 7, "ab"); checkEmit(iterator.next(), 6, 7, "ab");
} }
@Test @Test
public void nonOverlappingFirstMatch() { public void nonOverlappingFirstMatch() {
Trie trie = new Trie().removeOverlaps(); Trie trie = Trie.builder().removeOverlaps()
trie.addKeyword("ab"); .addKeyword("ab")
trie.addKeyword("cba"); .addKeyword("cba")
trie.addKeyword("ababc"); .addKeyword("ababc")
.build();
Emit firstMatch = trie.firstMatch("ababcbab"); Emit firstMatch = trie.firstMatch("ababcbab");
checkEmit(firstMatch, 0, 4, "ababc"); checkEmit(firstMatch, 0, 4, "ababc");
} }
@Test
public void containsMatch() {
Trie trie = Trie.builder().removeOverlaps()
.addKeyword("ab")
.addKeyword("cba")
.addKeyword("ababc")
.build();
assertTrue(trie.containsMatch("ababcbab"));
}
@Test @Test
public void startOfChurchillSpeech() { public void startOfChurchillSpeech() {
Trie trie = Trie.builder().removeOverlaps() Trie trie = Trie.builder().removeOverlaps()
@ -246,7 +265,8 @@ public class TrieTest {
@Test @Test
public void partialMatch() { public void partialMatch() {
Trie trie = Trie.builder().onlyWholeWords() Trie trie = Trie.builder()
.onlyWholeWords()
.addKeyword("sugar") .addKeyword("sugar")
.build(); .build();
Collection<Emit> emits = trie.parseText("sugarcane sugarcane sugar canesugar"); // left, middle, right test Collection<Emit> emits = trie.parseText("sugarcane sugarcane sugar canesugar"); // left, middle, right test
@ -256,8 +276,10 @@ public class TrieTest {
@Test @Test
public void partialMatchFirstMatch() { public void partialMatchFirstMatch() {
Trie trie = new Trie().onlyWholeWords(); Trie trie = Trie.builder()
trie.addKeyword("sugar"); .onlyWholeWords()
.addKeyword("sugar")
.build();
Emit firstMatch = trie.firstMatch("sugarcane sugarcane sugar canesugar"); // left, middle, right test Emit firstMatch = trie.firstMatch("sugarcane sugarcane sugar canesugar"); // left, middle, right test
checkEmit(firstMatch, 20, 24, "sugar"); checkEmit(firstMatch, 20, 24, "sugar");
@ -318,11 +340,12 @@ public class TrieTest {
@Test @Test
public void caseInsensitiveFirstMatch() { public void caseInsensitiveFirstMatch() {
Trie trie = new Trie().caseInsensitive(); Trie trie = Trie.builder().caseInsensitive()
trie.addKeyword("turning"); .addKeyword("turning")
trie.addKeyword("once"); .addKeyword("once")
trie.addKeyword("again"); .addKeyword("again")
trie.addKeyword("börkü"); .addKeyword("börkü")
.build();
Emit firstMatch = trie.firstMatch("TurninG OnCe AgAiN BÖRKÜ"); Emit firstMatch = trie.firstMatch("TurninG OnCe AgAiN BÖRKÜ");
checkEmit(firstMatch, 0, 6, "turning"); checkEmit(firstMatch, 0, 6, "turning");
@ -365,9 +388,12 @@ public class TrieTest {
@Test @Test
public void unicodeIssueBug8ReportedByDwyerkFirstMatch() { public void unicodeIssueBug8ReportedByDwyerkFirstMatch() {
String target = "LİKE THIS"; // The second character ('İ') is Unicode, which was read by AC as a 2-byte char String target = "LİKE THIS"; // The second character ('İ') is Unicode, which was read by AC as a 2-byte char
Trie trie = new Trie().caseInsensitive().onlyWholeWords(); Trie trie = Trie.builder()
assertEquals("THIS", target.substring(5,9)); // Java does it the right way .caseInsensitive()
trie.addKeyword("this"); .onlyWholeWords()
.addKeyword("this")
.build();
assertEquals("THIS", target.substring(5, 9)); // Java does it the right way
Emit firstMatch = trie.firstMatch(target); Emit firstMatch = trie.firstMatch(target);
checkEmit(firstMatch, 5, 8, "this"); checkEmit(firstMatch, 5, 8, "this");
} }