From 1785a554f3762d4916f61c2a417a127f222e7dd7 Mon Sep 17 00:00:00 2001 From: robert-bor Date: Fri, 31 Jan 2014 14:56:11 +0100 Subject: [PATCH] Issue #1 remove overlapping intervals. Resolution rule: longer matches over smaller ones, left-most over right-most --- README.md | 19 ++- pom.xml | 4 +- src/main/java/org/ahocorasick/Emit.java | 26 ---- .../org/ahocorasick/interval/Interval.java | 57 +++++++++ .../ahocorasick/interval/IntervalNode.java | 119 ++++++++++++++++++ .../ahocorasick/interval/IntervalTree.java | 48 +++++++ .../ahocorasick/interval/Intervalable.java | 9 ++ .../IntervalableComparatorByPosition.java | 12 ++ .../IntervalableComparatorBySize.java | 16 +++ src/main/java/org/ahocorasick/trie/Emit.java | 19 +++ .../org/ahocorasick/{ => trie}/State.java | 2 +- .../java/org/ahocorasick/{ => trie}/Trie.java | 26 +++- .../java/org/ahocorasick/trie/TrieConfig.java | 15 +++ .../ahocorasick/interval/IntervalTest.java | 57 +++++++++ .../interval/IntervalTreeTest.java | 51 ++++++++ .../IntervalableComparatorByPositionTest.java | 25 ++++ .../IntervalableComparatorBySizeTest.java | 35 ++++++ .../java/org/ahocorasick/trie/EmitTest.java | 24 ++++ .../org/ahocorasick/{ => trie}/StateTest.java | 3 +- .../org/ahocorasick/{ => trie}/TrieTest.java | 33 ++++- 20 files changed, 562 insertions(+), 38 deletions(-) delete mode 100644 src/main/java/org/ahocorasick/Emit.java create mode 100644 src/main/java/org/ahocorasick/interval/Interval.java create mode 100644 src/main/java/org/ahocorasick/interval/IntervalNode.java create mode 100644 src/main/java/org/ahocorasick/interval/IntervalTree.java create mode 100644 src/main/java/org/ahocorasick/interval/Intervalable.java create mode 100644 src/main/java/org/ahocorasick/interval/IntervalableComparatorByPosition.java create mode 100644 src/main/java/org/ahocorasick/interval/IntervalableComparatorBySize.java create mode 100644 src/main/java/org/ahocorasick/trie/Emit.java rename src/main/java/org/ahocorasick/{ => trie}/State.java (99%) rename src/main/java/org/ahocorasick/{ => trie}/Trie.java (83%) create mode 100644 src/main/java/org/ahocorasick/trie/TrieConfig.java create mode 100644 src/test/java/org/ahocorasick/interval/IntervalTest.java create mode 100644 src/test/java/org/ahocorasick/interval/IntervalTreeTest.java create mode 100644 src/test/java/org/ahocorasick/interval/IntervalableComparatorByPositionTest.java create mode 100644 src/test/java/org/ahocorasick/interval/IntervalableComparatorBySizeTest.java create mode 100644 src/test/java/org/ahocorasick/trie/EmitTest.java rename src/test/java/org/ahocorasick/{ => trie}/StateTest.java (90%) rename src/test/java/org/ahocorasick/{ => trie}/TrieTest.java (69%) diff --git a/README.md b/README.md index b32c965..843a336 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,17 @@ Aho-Corasick ============ +Dependency +---------- +Include this dependency in your POM. Be sure to check for the latest version in Maven Central. +```xml + + org.ahocorasick + ahocorasick + 0.1.0 + +``` + Introduction ------------ Nowadays most free-text searching is based on Lucene-like approaches, where the search text is parsed into its @@ -49,11 +60,9 @@ Setting up the Trie is a piece of cake: ``` You can now read the set. In this case it will find the following: -* "she" at position 3 -* "he" at position 3 -* "hers" at position 5 - -Note that the end-positions of the keyword match are emitted, not the start-positions! +* "she" starting at position 1, ending at position 3 +* "he" starting at position 2, ending at position 3 +* "hers" starting at position 2, ending at position 5 License ------- diff --git a/pom.xml b/pom.xml index 72f0bc2..0b0557f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,8 +1,8 @@ 4.0.0 - org.aho-corasick - efficient-string-matching + org.ahocorasick + ahocorasick 0.2.0-SNAPSHOT jar Aho-CoraSick algorithm for efficient string matching diff --git a/src/main/java/org/ahocorasick/Emit.java b/src/main/java/org/ahocorasick/Emit.java deleted file mode 100644 index dfc96fb..0000000 --- a/src/main/java/org/ahocorasick/Emit.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.ahocorasick; - -public class Emit { - private final int start; - private final int end; - private final String keyword; - - public Emit(final int start, final int end, final String keyword) { - this.start = start; - this.end = end; - this.keyword = keyword; - } - - public int getStart() { - return this.start; - } - - public int getEnd() { - return this.end; - } - - public String getKeyword() { - return this.keyword; - } - -} diff --git a/src/main/java/org/ahocorasick/interval/Interval.java b/src/main/java/org/ahocorasick/interval/Interval.java new file mode 100644 index 0000000..b457500 --- /dev/null +++ b/src/main/java/org/ahocorasick/interval/Interval.java @@ -0,0 +1,57 @@ +package org.ahocorasick.interval; + +public class Interval implements Intervalable { + + private int start; + private int end; + + public Interval(final int start, final int end) { + this.start = start; + this.end = end; + } + + public int getStart() { + return this.start; + } + + public int getEnd() { + return this.end; + } + + public int size() { + return end - start + 1; + } + + public boolean overlapsWith(Interval other) { + return this.start <= other.getEnd() && + this.end >= other.getStart(); + } + + public boolean overlapsWith(int point) { + return this.start <= point && point <= this.end; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Intervalable)) { + return false; + } + Intervalable other = (Intervalable)o; + return this.start == other.getStart() && + this.end == other.getEnd(); + } + + @Override + public int hashCode() { + return this.start % 100 + this.end % 100; + } + + @Override + public int compareTo(Object o) { + if (!(o instanceof Intervalable)) { + return -1; + } + Intervalable other = (Intervalable)o; + return this.start - other.getStart(); + } +} diff --git a/src/main/java/org/ahocorasick/interval/IntervalNode.java b/src/main/java/org/ahocorasick/interval/IntervalNode.java new file mode 100644 index 0000000..11db0ae --- /dev/null +++ b/src/main/java/org/ahocorasick/interval/IntervalNode.java @@ -0,0 +1,119 @@ +package org.ahocorasick.interval; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class IntervalNode { + + private enum Direction { LEFT, RIGHT } + + private IntervalNode left = null; + private IntervalNode right = null; + private int point; + private List intervals = new ArrayList(); + + public IntervalNode(List intervals) { + this.point = determineMedian(intervals); + + List toLeft = new ArrayList(); + List toRight = new ArrayList(); + + for (Intervalable interval : intervals) { + if (interval.getEnd() < this.point) { + toLeft.add(interval); + } else if (interval.getStart() > this.point) { + toRight.add(interval); + } else { + this.intervals.add(interval); + } + } + + if (toLeft.size() > 0) { + this.left = new IntervalNode(toLeft); + } + if (toRight.size() > 0) { + this.right = new IntervalNode(toRight); + } + } + + public int determineMedian(List intervals) { + int start = -1; + int end = -1; + for (Intervalable interval : intervals) { + int currentStart = interval.getStart(); + int currentEnd = interval.getEnd(); + if (start == -1 || currentStart < start) { + start = currentStart; + } + if (end == -1 || currentEnd > end) { + end = currentEnd; + } + } + return (start + end) / 2; + } + + public List findOverlaps(Intervalable interval) { + + List overlaps = new ArrayList(); + + if (this.point < interval.getStart()) { // Tends to the right + addToOverlaps(interval, overlaps, findOverlappingRanges(this.right, interval)); + addToOverlaps(interval, overlaps, checkForOverlapsToTheRight(interval)); + } else if (this.point > interval.getEnd()) { // Tends to the left + addToOverlaps(interval, overlaps, findOverlappingRanges(this.left, interval)); + addToOverlaps(interval, overlaps, checkForOverlapsToTheLeft(interval)); + } else { // Somewhere in the middle + addToOverlaps(interval, overlaps, this.intervals); + addToOverlaps(interval, overlaps, findOverlappingRanges(this.left, interval)); + addToOverlaps(interval, overlaps, findOverlappingRanges(this.right, interval)); + } + + return overlaps; + } + + protected void addToOverlaps(Intervalable interval, List overlaps, List newOverlaps) { + for (Intervalable currentInterval : newOverlaps) { + if (!currentInterval.equals(interval)) { + overlaps.add(currentInterval); + } + } + } + + protected List checkForOverlapsToTheLeft(Intervalable interval) { + return checkForOverlaps(interval, Direction.LEFT); + } + + protected List checkForOverlapsToTheRight(Intervalable interval) { + return checkForOverlaps(interval, Direction.RIGHT); + } + + protected List checkForOverlaps(Intervalable interval, Direction direction) { + + List overlaps = new ArrayList(); + for (Intervalable currentInterval : this.intervals) { + switch (direction) { + case LEFT : + if (currentInterval.getStart() <= interval.getEnd()) { + overlaps.add(currentInterval); + } + break; + case RIGHT : + if (currentInterval.getEnd() >= interval.getStart()) { + overlaps.add(currentInterval); + } + break; + } + } + return overlaps; + } + + + protected List findOverlappingRanges(IntervalNode node, Intervalable interval) { + if (node != null) { + return node.findOverlaps(interval); + } + return Collections.emptyList(); + } + +} diff --git a/src/main/java/org/ahocorasick/interval/IntervalTree.java b/src/main/java/org/ahocorasick/interval/IntervalTree.java new file mode 100644 index 0000000..40eeb0b --- /dev/null +++ b/src/main/java/org/ahocorasick/interval/IntervalTree.java @@ -0,0 +1,48 @@ +package org.ahocorasick.interval; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +public class IntervalTree { + + private IntervalNode rootNode = null; + + public IntervalTree(List intervals) { + this.rootNode = new IntervalNode(intervals); + } + + public List removeOverlaps(List intervals) { + + // Sort the intervals on size, then left-most position + Collections.sort(intervals, new IntervalableComparatorBySize()); + + Set removeIntervals = new TreeSet(); + + for (Intervalable interval : intervals) { + // If the interval was already removed, ignore it + if (removeIntervals.contains(interval)) { + continue; + } + + // Remove all overallping intervals + removeIntervals.addAll(findOverlaps(interval)); + } + + // Remove all intervals that were overlapping + for (Intervalable removeInterval : removeIntervals) { + intervals.remove(removeInterval); + } + + // Sort the intervals, now on left-most position only + Collections.sort(intervals, new IntervalableComparatorByPosition()); + + return intervals; + } + + public List findOverlaps(Intervalable interval) { + return rootNode.findOverlaps(interval); + } + +} diff --git a/src/main/java/org/ahocorasick/interval/Intervalable.java b/src/main/java/org/ahocorasick/interval/Intervalable.java new file mode 100644 index 0000000..286a232 --- /dev/null +++ b/src/main/java/org/ahocorasick/interval/Intervalable.java @@ -0,0 +1,9 @@ +package org.ahocorasick.interval; + +public interface Intervalable extends Comparable { + + public int getStart(); + public int getEnd(); + public int size(); + +} diff --git a/src/main/java/org/ahocorasick/interval/IntervalableComparatorByPosition.java b/src/main/java/org/ahocorasick/interval/IntervalableComparatorByPosition.java new file mode 100644 index 0000000..d144995 --- /dev/null +++ b/src/main/java/org/ahocorasick/interval/IntervalableComparatorByPosition.java @@ -0,0 +1,12 @@ +package org.ahocorasick.interval; + +import java.util.Comparator; + +public class IntervalableComparatorByPosition implements Comparator { + + @Override + public int compare(Intervalable intervalable, Intervalable intervalable2) { + return intervalable.getStart() - intervalable2.getStart(); + } + +} diff --git a/src/main/java/org/ahocorasick/interval/IntervalableComparatorBySize.java b/src/main/java/org/ahocorasick/interval/IntervalableComparatorBySize.java new file mode 100644 index 0000000..3814759 --- /dev/null +++ b/src/main/java/org/ahocorasick/interval/IntervalableComparatorBySize.java @@ -0,0 +1,16 @@ +package org.ahocorasick.interval; + +import java.util.Comparator; + +public class IntervalableComparatorBySize implements Comparator { + + @Override + public int compare(Intervalable intervalable, Intervalable intervalable2) { + int comparison = intervalable2.size() - intervalable.size(); + if (comparison == 0) { + comparison = intervalable.getStart() - intervalable2.getStart(); + } + return comparison; + } + +} diff --git a/src/main/java/org/ahocorasick/trie/Emit.java b/src/main/java/org/ahocorasick/trie/Emit.java new file mode 100644 index 0000000..d6644ef --- /dev/null +++ b/src/main/java/org/ahocorasick/trie/Emit.java @@ -0,0 +1,19 @@ +package org.ahocorasick.trie; + +import org.ahocorasick.interval.Interval; +import org.ahocorasick.interval.Intervalable; + +public class Emit extends Interval implements Intervalable { + + private final String keyword; + + public Emit(final int start, final int end, final String keyword) { + super(start, end); + this.keyword = keyword; + } + + public String getKeyword() { + return this.keyword; + } + +} diff --git a/src/main/java/org/ahocorasick/State.java b/src/main/java/org/ahocorasick/trie/State.java similarity index 99% rename from src/main/java/org/ahocorasick/State.java rename to src/main/java/org/ahocorasick/trie/State.java index 535f70d..9108838 100644 --- a/src/main/java/org/ahocorasick/State.java +++ b/src/main/java/org/ahocorasick/trie/State.java @@ -1,4 +1,4 @@ -package org.ahocorasick; +package org.ahocorasick.trie; import java.util.*; diff --git a/src/main/java/org/ahocorasick/Trie.java b/src/main/java/org/ahocorasick/trie/Trie.java similarity index 83% rename from src/main/java/org/ahocorasick/Trie.java rename to src/main/java/org/ahocorasick/trie/Trie.java index 16d8e88..769402a 100644 --- a/src/main/java/org/ahocorasick/Trie.java +++ b/src/main/java/org/ahocorasick/trie/Trie.java @@ -1,4 +1,7 @@ -package org.ahocorasick; +package org.ahocorasick.trie; + +import org.ahocorasick.interval.IntervalTree; +import org.ahocorasick.interval.Intervalable; import java.util.ArrayList; import java.util.Collection; @@ -13,14 +16,26 @@ import java.util.concurrent.LinkedBlockingDeque; */ public class Trie { + private TrieConfig trieConfig; + private State rootState; private boolean failureStatesConstructed = false; - public Trie() { + public Trie(TrieConfig trieConfig) { + this.trieConfig = trieConfig; this.rootState = new State(); } + public Trie() { + this(new TrieConfig()); + } + + public Trie removeOverlaps() { + this.trieConfig.setAllowOverlaps(false); + return this; + } + public void addKeyword(String keyword) { State currentState = this.rootState; @@ -30,6 +45,7 @@ public class Trie { currentState.addEmit(keyword); } + @SuppressWarnings("unchecked") public Collection parseText(String text) { checkForConstructedFailureStates(); @@ -41,6 +57,12 @@ public class Trie { storeEmits(position, currentState, collectedEmits); position++; } + + if (!trieConfig.isAllowOverlaps()) { + IntervalTree intervalTree = new IntervalTree((List)(List)collectedEmits); + intervalTree.removeOverlaps((List) (List) collectedEmits); + } + return collectedEmits; } diff --git a/src/main/java/org/ahocorasick/trie/TrieConfig.java b/src/main/java/org/ahocorasick/trie/TrieConfig.java new file mode 100644 index 0000000..23bed1f --- /dev/null +++ b/src/main/java/org/ahocorasick/trie/TrieConfig.java @@ -0,0 +1,15 @@ +package org.ahocorasick.trie; + +public class TrieConfig { + + private boolean allowOverlaps = true; + + public boolean isAllowOverlaps() { + return allowOverlaps; + } + + public void setAllowOverlaps(boolean allowOverlaps) { + this.allowOverlaps = allowOverlaps; + } + +} diff --git a/src/test/java/org/ahocorasick/interval/IntervalTest.java b/src/test/java/org/ahocorasick/interval/IntervalTest.java new file mode 100644 index 0000000..e61bad7 --- /dev/null +++ b/src/test/java/org/ahocorasick/interval/IntervalTest.java @@ -0,0 +1,57 @@ +package org.ahocorasick.interval; + +import org.junit.Test; + +import java.util.*; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +public class IntervalTest { + + @Test + public void construct() { + Interval i = new Interval(1,3); + assertEquals(1, i.getStart()); + assertEquals(3, i.getEnd()); + } + + @Test + public void size() { + assertEquals(3, new Interval(0,2).size()); + } + + @Test + public void intervaloverlaps() { + assertTrue(new Interval(1,3).overlapsWith(new Interval(2,4))); + } + + @Test + public void intervalDoesNotOverlap() { + assertFalse(new Interval(1, 13).overlapsWith(new Interval(27, 42))); + } + + @Test + public void pointOverlaps() { + assertTrue(new Interval(1,3).overlapsWith(2)); + } + + @Test + public void pointDoesNotOverlap() { + assertFalse(new Interval(1, 13).overlapsWith(42)); + } + + @Test + public void comparable() { + Set intervals = new TreeSet(); + intervals.add(new Interval(4, 6)); + intervals.add(new Interval(2, 7)); + intervals.add(new Interval(3, 4)); + Iterator it = intervals.iterator(); + assertEquals(2, it.next().getStart()); + assertEquals(3, it.next().getStart()); + assertEquals(4, it.next().getStart()); + } + +} diff --git a/src/test/java/org/ahocorasick/interval/IntervalTreeTest.java b/src/test/java/org/ahocorasick/interval/IntervalTreeTest.java new file mode 100644 index 0000000..f4a7f57 --- /dev/null +++ b/src/test/java/org/ahocorasick/interval/IntervalTreeTest.java @@ -0,0 +1,51 @@ +package org.ahocorasick.interval; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import static junit.framework.Assert.assertEquals; + +public class IntervalTreeTest { + + @Test + public void findOverlaps() { + List intervals = new ArrayList(); + intervals.add(new Interval(0, 2)); + intervals.add(new Interval(1, 3)); + intervals.add(new Interval(2, 4)); + intervals.add(new Interval(3, 5)); + intervals.add(new Interval(4, 6)); + intervals.add(new Interval(5, 7)); + IntervalTree intervalTree = new IntervalTree(intervals); + List overlaps = intervalTree.findOverlaps(new Interval(1,3)); + assertEquals(3, overlaps.size()); + Iterator overlapsIt = overlaps.iterator(); + assertOverlap(overlapsIt.next(), 2, 4); + assertOverlap(overlapsIt.next(), 3, 5); + assertOverlap(overlapsIt.next(), 0, 2); + } + + @Test + public void removeOverlaps() { + List intervals = new ArrayList(); + intervals.add(new Interval(0, 2)); + intervals.add(new Interval(4, 5)); + intervals.add(new Interval(2, 10)); + intervals.add(new Interval(6, 13)); + intervals.add(new Interval(9, 15)); + intervals.add(new Interval(12, 16)); + IntervalTree intervalTree = new IntervalTree(intervals); + intervals = intervalTree.removeOverlaps(intervals); + assertEquals(2, intervals.size()); + + } + + protected void assertOverlap(Intervalable interval, int expectedStart, int expectedEnd) { + assertEquals(expectedStart, interval.getStart()); + assertEquals(expectedEnd, interval.getEnd()); + } + +} diff --git a/src/test/java/org/ahocorasick/interval/IntervalableComparatorByPositionTest.java b/src/test/java/org/ahocorasick/interval/IntervalableComparatorByPositionTest.java new file mode 100644 index 0000000..a6f1017 --- /dev/null +++ b/src/test/java/org/ahocorasick/interval/IntervalableComparatorByPositionTest.java @@ -0,0 +1,25 @@ +package org.ahocorasick.interval; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static junit.framework.Assert.assertEquals; + +public class IntervalableComparatorByPositionTest { + + @Test + public void sortOnPosition() { + List intervals = new ArrayList(); + intervals.add(new Interval(4,5)); + intervals.add(new Interval(1,4)); + intervals.add(new Interval(3,8)); + Collections.sort(intervals, new IntervalableComparatorByPosition()); + assertEquals(4, intervals.get(0).size()); + assertEquals(6, intervals.get(1).size()); + assertEquals(2, intervals.get(2).size()); + } + +} diff --git a/src/test/java/org/ahocorasick/interval/IntervalableComparatorBySizeTest.java b/src/test/java/org/ahocorasick/interval/IntervalableComparatorBySizeTest.java new file mode 100644 index 0000000..208cf3d --- /dev/null +++ b/src/test/java/org/ahocorasick/interval/IntervalableComparatorBySizeTest.java @@ -0,0 +1,35 @@ +package org.ahocorasick.interval; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static junit.framework.Assert.assertEquals; + +public class IntervalableComparatorBySizeTest { + + @Test + public void sortOnSize() { + List intervals = new ArrayList(); + intervals.add(new Interval(4,5)); + intervals.add(new Interval(1,4)); + intervals.add(new Interval(3,8)); + Collections.sort(intervals, new IntervalableComparatorBySize()); + assertEquals(6, intervals.get(0).size()); + assertEquals(4, intervals.get(1).size()); + assertEquals(2, intervals.get(2).size()); + } + + @Test + public void sortOnSizeThenPosition() { + List intervals = new ArrayList(); + intervals.add(new Interval(4,7)); + intervals.add(new Interval(2,5)); + Collections.sort(intervals, new IntervalableComparatorBySize()); + assertEquals(2, intervals.get(0).getStart()); + assertEquals(4, intervals.get(1).getStart()); + } + +} diff --git a/src/test/java/org/ahocorasick/trie/EmitTest.java b/src/test/java/org/ahocorasick/trie/EmitTest.java new file mode 100644 index 0000000..248fcc9 --- /dev/null +++ b/src/test/java/org/ahocorasick/trie/EmitTest.java @@ -0,0 +1,24 @@ +package org.ahocorasick.trie; + +import org.junit.Test; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotSame; + +public class EmitTest { + + @Test + public void equals() { + Emit one = new Emit(13, 42, null); + Emit two = new Emit(13, 42, null); + assertEquals(one, two); + } + + @Test + public void notEquals() { + Emit one = new Emit(13, 42, null); + Emit two = new Emit(13, 43, null); + assertNotSame(one, two); + } + +} diff --git a/src/test/java/org/ahocorasick/StateTest.java b/src/test/java/org/ahocorasick/trie/StateTest.java similarity index 90% rename from src/test/java/org/ahocorasick/StateTest.java rename to src/test/java/org/ahocorasick/trie/StateTest.java index 96139b1..2a64370 100644 --- a/src/test/java/org/ahocorasick/StateTest.java +++ b/src/test/java/org/ahocorasick/trie/StateTest.java @@ -1,5 +1,6 @@ -package org.ahocorasick; +package org.ahocorasick.trie; +import org.ahocorasick.trie.State; import org.junit.Test; import static junit.framework.Assert.assertEquals; diff --git a/src/test/java/org/ahocorasick/TrieTest.java b/src/test/java/org/ahocorasick/trie/TrieTest.java similarity index 69% rename from src/test/java/org/ahocorasick/TrieTest.java rename to src/test/java/org/ahocorasick/trie/TrieTest.java index ac16471..17019f9 100644 --- a/src/test/java/org/ahocorasick/TrieTest.java +++ b/src/test/java/org/ahocorasick/trie/TrieTest.java @@ -1,5 +1,7 @@ -package org.ahocorasick; +package org.ahocorasick.trie; +import org.ahocorasick.trie.Emit; +import org.ahocorasick.trie.Trie; import org.junit.Test; import java.util.Collection; @@ -77,6 +79,35 @@ public class TrieTest { checkEmit(iterator.next(), 51, 58, "broccoli"); } + @Test + public void longAndShortOverlappingMatch() { + Trie trie = new Trie(); + trie.addKeyword("he"); + trie.addKeyword("hehehehe"); + Collection emits = trie.parseText("hehehehehe"); + Iterator iterator = emits.iterator(); + checkEmit(iterator.next(), 0, 1, "he"); + checkEmit(iterator.next(), 2, 3, "he"); + checkEmit(iterator.next(), 4, 5, "he"); + checkEmit(iterator.next(), 0, 7, "hehehehe"); + checkEmit(iterator.next(), 6, 7, "he"); + checkEmit(iterator.next(), 2, 9, "hehehehe"); + checkEmit(iterator.next(), 8, 9, "he"); + } + + @Test + public void nonOverlapping() { + Trie trie = new Trie().removeOverlaps(); + trie.addKeyword("ab"); + trie.addKeyword("cba"); + trie.addKeyword("ababc"); + Collection emits = trie.parseText("ababcbab"); + Iterator iterator = emits.iterator(); + // With overlaps: ab@1, ab@3, ababc@4, cba@6, ab@7 + checkEmit(iterator.next(), 0, 4, "ababc"); + checkEmit(iterator.next(), 6, 7, "ab"); + } + private void checkEmit(Emit next, int expectedStart, int expectedEnd, String expectedKeyword) { assertEquals(expectedStart, next.getStart()); assertEquals(expectedEnd, next.getEnd());