From c02e762a2bb3f5bb294794bd81e08984b92a528e Mon Sep 17 00:00:00 2001 From: macroscopic64 Date: Tue, 17 Dec 2019 07:46:24 +0530 Subject: [PATCH 1/5] [BAEL-3485] - Java Range lookup problem --- .../baeldung/algorithms/quadtree/Point.java | 24 ++++ .../algorithms/quadtree/QuadTree.java | 109 ++++++++++++++++++ .../baeldung/algorithms/quadtree/Region.java | 85 ++++++++++++++ .../quadtree/QuadTreeSearchTest.java | 60 ++++++++++ 4 files changed, 278 insertions(+) create mode 100644 algorithms-searching/src/main/java/com/baeldung/algorithms/quadtree/Point.java create mode 100644 algorithms-searching/src/main/java/com/baeldung/algorithms/quadtree/QuadTree.java create mode 100644 algorithms-searching/src/main/java/com/baeldung/algorithms/quadtree/Region.java create mode 100644 algorithms-searching/src/test/java/com/baeldung/algorithms/quadtree/QuadTreeSearchTest.java diff --git a/algorithms-searching/src/main/java/com/baeldung/algorithms/quadtree/Point.java b/algorithms-searching/src/main/java/com/baeldung/algorithms/quadtree/Point.java new file mode 100644 index 0000000000..f61ee87f7d --- /dev/null +++ b/algorithms-searching/src/main/java/com/baeldung/algorithms/quadtree/Point.java @@ -0,0 +1,24 @@ +package com.baeldung.algorithms.quadtree; + +public class Point { + private float x; + private float y; + + public Point(float x, float y) { + this.x = x; + this.y = y; + } + + public float getX() { + return x; + } + + public float getY() { + return y; + } + + @Override + public String toString() { + return "[" + x + " , " + y + "]"; + } +} diff --git a/algorithms-searching/src/main/java/com/baeldung/algorithms/quadtree/QuadTree.java b/algorithms-searching/src/main/java/com/baeldung/algorithms/quadtree/QuadTree.java new file mode 100644 index 0000000000..bb3cf029b1 --- /dev/null +++ b/algorithms-searching/src/main/java/com/baeldung/algorithms/quadtree/QuadTree.java @@ -0,0 +1,109 @@ +package com.baeldung.algorithms.quadtree; + +import java.util.ArrayList; +import java.util.List; + +public class QuadTree { + private static final int MAX_POINTS = 3; + private Region area; + private List points = new ArrayList<>(); + private List quadTrees = new ArrayList<>(); + private StringBuilder searchTraversePath; + + public QuadTree(Region area) { + this.area = area; + } + + public boolean addPoint(Point point) { + if (this.area.containsPoint(point)) { + if (this.points.size() < MAX_POINTS) { + this.points.add(point); + return true; + } else { + if (this.quadTrees.size() == 0) { + createQuadrants(); + } + return addPointToOneQuadrant(point); + } + } + return false; + } + + private boolean addPointToOneQuadrant(Point point) { + boolean isPointAdded; + for (int i = 0; i < 4; i++) { + isPointAdded = this.quadTrees.get(i) + .addPoint(point); + if (isPointAdded) + return true; + } + return false; + } + + private void createQuadrants() { + Region region; + for (int i = 0; i < 4; i++) { + region = this.area.getQuadrant(i); + quadTrees.add(new QuadTree(region)); + } + } + + public List search(Region searchRegion, List matches, String depthIndicator) { + searchTraversePath = new StringBuilder(); + if (matches == null) { + matches = new ArrayList(); + searchTraversePath.append(depthIndicator) + .append("Search Boundary =") + .append(searchRegion) + .append("\n"); + } + if (!this.area.doesOverlap(searchRegion)) { + return matches; + } else { + for (Point point : points) { + if (searchRegion.containsPoint(point)) { + searchTraversePath.append(depthIndicator) + .append("Found match " + point) + .append("\n"); + matches.add(point); + } + } + if (this.quadTrees.size() > 0) { + for (int i = 0; i < 4; i++) { + searchTraversePath.append(depthIndicator) + .append("Q") + .append(i) + .append("-->") + .append(quadTrees.get(i).area) + .append("\n"); + quadTrees.get(i) + .search(searchRegion, matches, depthIndicator + "\t"); + this.searchTraversePath.append(quadTrees.get(i) + .printSearchTraversePath()); + } + } + } + return matches; + } + + public String printTree(String depthIndicator) { + String str = ""; + if (depthIndicator == "") { + str += "Root-->" + area.toString() + "\n"; + } + + for (Point point : points) { + str += depthIndicator + point.toString() + "\n"; + } + for (int i = 0; i < quadTrees.size(); i++) { + str += depthIndicator + "Q" + String.valueOf(i) + "-->" + quadTrees.get(i).area.toString() + "\n"; + str += quadTrees.get(i) + .printTree(depthIndicator + "\t"); + } + return str; + } + + public String printSearchTraversePath() { + return searchTraversePath.toString(); + } +} diff --git a/algorithms-searching/src/main/java/com/baeldung/algorithms/quadtree/Region.java b/algorithms-searching/src/main/java/com/baeldung/algorithms/quadtree/Region.java new file mode 100644 index 0000000000..600711c4ae --- /dev/null +++ b/algorithms-searching/src/main/java/com/baeldung/algorithms/quadtree/Region.java @@ -0,0 +1,85 @@ +package com.baeldung.algorithms.quadtree; + +public class Region { + private float x1; + private float y1; + private float x2; + private float y2; + + public Region(float x1, float y1, float x2, float y2) { + if (x1 >= x2 || y1 >= y2) + throw new IllegalArgumentException("(x1,y1) should be lesser than (x2,y2)"); + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + } + + public Region getQuadrant(int quadrantIndex) { + float quadrantWidth = (this.x2 - this.x1) / 2; + float quadrantHeight = (this.y2 - this.y1) / 2; + + // 0=SW, 1=NW, 2=NE, 3=SE + switch (quadrantIndex) { + case 0: + return new Region(x1, y1, x1 + quadrantWidth, y1 + quadrantHeight); + case 1: + return new Region(x1, y1 + quadrantHeight, x1 + quadrantWidth, y2); + case 2: + return new Region(x1 + quadrantWidth, y1 + quadrantHeight, x2, y2); + case 3: + return new Region(x1 + quadrantWidth, y1, x2, y1 + quadrantHeight); + } + return null; + } + + public boolean containsPoint(Point point) { + // Consider left and top side to be inclusive for points on border + return point.getX() >= this.x1 + && point.getX() < this.x2 + && point.getY() >= this.y1 + && point.getY() < this.y2; + } + + public boolean doesOverlap(Region testRegion) { + // Is test region completely to left of my region? + if (testRegion.getX2() < this.getX1()) { + return false; + } + // Is test region completely to right of my region? + if (testRegion.getX1() > this.getX2()) { + return false; + } + // Is test region completely above my region? + if (testRegion.getY1() > this.getY2()) { + return false; + } + // Is test region completely below my region? + if (testRegion.getY2() < this.getY1()) { + return false; + } + return true; + } + + @Override + public String toString() { + return "[Region (x1=" + x1 + ", y1=" + y1 + "), (x2=" + x2 + ", y2=" + y2 + ")]"; + } + + public float getX1() { + return x1; + } + + public float getY1() { + return y1; + } + + public float getX2() { + return x2; + } + + public float getY2() { + return y2; + } + +} diff --git a/algorithms-searching/src/test/java/com/baeldung/algorithms/quadtree/QuadTreeSearchTest.java b/algorithms-searching/src/test/java/com/baeldung/algorithms/quadtree/QuadTreeSearchTest.java new file mode 100644 index 0000000000..4c810a4724 --- /dev/null +++ b/algorithms-searching/src/test/java/com/baeldung/algorithms/quadtree/QuadTreeSearchTest.java @@ -0,0 +1,60 @@ +package com.baeldung.algorithms.quadtree; + +import org.junit.Assert; + +import java.util.List; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class QuadTreeSearchTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(QuadTreeSearchTest.class); + + private static QuadTree quadTree; + + @BeforeClass + public static void setUp() { + Region area = new Region(0, 0, 400, 400); + quadTree = new QuadTree(area); + + float[][] points = new float[][] { { 21, 25 }, { 55, 53 }, { 70, 318 }, { 98, 302 }, + { 49, 229 }, { 135, 229 }, { 224, 292 }, { 206, 321 }, { 197, 258 }, { 245, 238 } }; + + for (int i = 0; i < points.length; i++) { + Point point = new Point(points[i][0], points[i][1]); + quadTree.addPoint(point); + } + LOGGER.debug("\n" + quadTree.printTree("")); + LOGGER.debug("=============================================="); + } + + @Test + public void givenQuadTree_whenSearchingForRange_thenReturn1MatchingItem() { + Region searchArea = new Region(200, 200, 250, 250); + List result = quadTree.search(searchArea, null, ""); + LOGGER.debug(result.toString()); + LOGGER.debug(quadTree.printSearchTraversePath()); + + Assert.assertEquals(1, result.size()); + Assert.assertArrayEquals(new float[] { 245, 238 }, + new float[]{result.get(0).getX(), result.get(0).getY() }, 0); + } + + @Test + public void givenQuadTree_whenSearchingForRange_thenReturn2MatchingItems() { + Region searchArea = new Region(0, 0, 100, 100); + List result = quadTree.search(searchArea, null, ""); + LOGGER.debug(result.toString()); + LOGGER.debug(quadTree.printSearchTraversePath()); + + Assert.assertEquals(2, result.size()); + Assert.assertArrayEquals(new float[] { 21, 25 }, + new float[]{result.get(0).getX(), result.get(0).getY() }, 0); + Assert.assertArrayEquals(new float[] { 55, 53 }, + new float[]{result.get(1).getX(), result.get(1).getY() }, 0); + + } +} From 0d9a2e739b898e4d3585672979c6e759fb6e96b1 Mon Sep 17 00:00:00 2001 From: macroscopic64 Date: Tue, 17 Dec 2019 08:47:46 +0530 Subject: [PATCH 2/5] [BAEL-3485] - Java Range lookup problem --- .../algorithms/quadtree/QuadTreeSearchTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/algorithms-searching/src/test/java/com/baeldung/algorithms/quadtree/QuadTreeSearchTest.java b/algorithms-searching/src/test/java/com/baeldung/algorithms/quadtree/QuadTreeSearchTest.java index 4c810a4724..e7240e76b5 100644 --- a/algorithms-searching/src/test/java/com/baeldung/algorithms/quadtree/QuadTreeSearchTest.java +++ b/algorithms-searching/src/test/java/com/baeldung/algorithms/quadtree/QuadTreeSearchTest.java @@ -27,16 +27,16 @@ public class QuadTreeSearchTest { Point point = new Point(points[i][0], points[i][1]); quadTree.addPoint(point); } - LOGGER.debug("\n" + quadTree.printTree("")); - LOGGER.debug("=============================================="); + LOGGER.info("\n" + quadTree.printTree("")); + LOGGER.info("=============================================="); } @Test public void givenQuadTree_whenSearchingForRange_thenReturn1MatchingItem() { Region searchArea = new Region(200, 200, 250, 250); List result = quadTree.search(searchArea, null, ""); - LOGGER.debug(result.toString()); - LOGGER.debug(quadTree.printSearchTraversePath()); + LOGGER.info(result.toString()); + LOGGER.info(quadTree.printSearchTraversePath()); Assert.assertEquals(1, result.size()); Assert.assertArrayEquals(new float[] { 245, 238 }, @@ -47,8 +47,8 @@ public class QuadTreeSearchTest { public void givenQuadTree_whenSearchingForRange_thenReturn2MatchingItems() { Region searchArea = new Region(0, 0, 100, 100); List result = quadTree.search(searchArea, null, ""); - LOGGER.debug(result.toString()); - LOGGER.debug(quadTree.printSearchTraversePath()); + LOGGER.info(result.toString()); + LOGGER.info(quadTree.printSearchTraversePath()); Assert.assertEquals(2, result.size()); Assert.assertArrayEquals(new float[] { 21, 25 }, From 11e6be8a05f38a9da6b5cc4938461d722e538e77 Mon Sep 17 00:00:00 2001 From: macroscopic64 Date: Tue, 17 Dec 2019 08:54:57 +0530 Subject: [PATCH 3/5] [BAEL-3485] - Java Range lookup problem --- .../{QuadTreeSearchTest.java => QuadTreeSearchUnitTest.java} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename algorithms-searching/src/test/java/com/baeldung/algorithms/quadtree/{QuadTreeSearchTest.java => QuadTreeSearchUnitTest.java} (96%) diff --git a/algorithms-searching/src/test/java/com/baeldung/algorithms/quadtree/QuadTreeSearchTest.java b/algorithms-searching/src/test/java/com/baeldung/algorithms/quadtree/QuadTreeSearchUnitTest.java similarity index 96% rename from algorithms-searching/src/test/java/com/baeldung/algorithms/quadtree/QuadTreeSearchTest.java rename to algorithms-searching/src/test/java/com/baeldung/algorithms/quadtree/QuadTreeSearchUnitTest.java index e7240e76b5..0b58ae9f14 100644 --- a/algorithms-searching/src/test/java/com/baeldung/algorithms/quadtree/QuadTreeSearchTest.java +++ b/algorithms-searching/src/test/java/com/baeldung/algorithms/quadtree/QuadTreeSearchUnitTest.java @@ -9,9 +9,9 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class QuadTreeSearchTest { +public class QuadTreeSearchUnitTest { - private static final Logger LOGGER = LoggerFactory.getLogger(QuadTreeSearchTest.class); + private static final Logger LOGGER = LoggerFactory.getLogger(QuadTreeSearchUnitTest.class); private static QuadTree quadTree; From acb317d17164a9abdb49731a6f66bd4573a88b66 Mon Sep 17 00:00:00 2001 From: macroscopic64 Date: Fri, 7 Feb 2020 23:08:53 +0530 Subject: [PATCH 4/5] BAEL-3480 Java Fast pattern matching using trie and suffix tree --- .../baeldung/algorithms/suffixtree/Node.java | 57 ++++++ .../algorithms/suffixtree/SuffixTree.java | 178 ++++++++++++++++++ .../suffixtree/SuffixTreeUnitTest.java | 78 ++++++++ 3 files changed, 313 insertions(+) create mode 100644 algorithms-searching/src/main/java/com/baeldung/algorithms/suffixtree/Node.java create mode 100644 algorithms-searching/src/main/java/com/baeldung/algorithms/suffixtree/SuffixTree.java create mode 100644 algorithms-searching/src/test/java/com/baeldung/algorithms/suffixtree/SuffixTreeUnitTest.java diff --git a/algorithms-searching/src/main/java/com/baeldung/algorithms/suffixtree/Node.java b/algorithms-searching/src/main/java/com/baeldung/algorithms/suffixtree/Node.java new file mode 100644 index 0000000000..5a77b09753 --- /dev/null +++ b/algorithms-searching/src/main/java/com/baeldung/algorithms/suffixtree/Node.java @@ -0,0 +1,57 @@ +package com.baeldung.algorithms.suffixtree; + +import java.util.ArrayList; +import java.util.List; + +public class Node { + private String text; + private List children; + private int position; + + public Node(String word, int position) { + this.text = word; + this.position = position; + this.children = new ArrayList<>(); + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public int getPosition() { + return position; + } + + public void setPosition(int position) { + this.position = position; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public String printTree(String depthIndicator) { + String str = ""; + String positionStr = position > -1 ? "[" + String.valueOf(position) + "]" : ""; + str += depthIndicator + text + positionStr + "\n"; + + for (int i = 0; i < children.size(); i++) { + str += children.get(i) + .printTree(depthIndicator + "\t"); + } + return str; + } + + @Override + public String toString() { + return printTree(""); + } +} \ No newline at end of file diff --git a/algorithms-searching/src/main/java/com/baeldung/algorithms/suffixtree/SuffixTree.java b/algorithms-searching/src/main/java/com/baeldung/algorithms/suffixtree/SuffixTree.java new file mode 100644 index 0000000000..50d9702cd3 --- /dev/null +++ b/algorithms-searching/src/main/java/com/baeldung/algorithms/suffixtree/SuffixTree.java @@ -0,0 +1,178 @@ +package com.baeldung.algorithms.suffixtree; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SuffixTree { + private static final Logger LOGGER = LoggerFactory.getLogger(SuffixTree.class); + private static final String WORD_TERMINATION = "$"; + private static final int POSITION_UNDEFINED = -1; + private Node root; + private String fullText; + + public SuffixTree() { + root = new Node("", POSITION_UNDEFINED); + fullText = ""; + } + + public void addText(String text) { + for (int i = 0; i < text.length(); i++) { + addSuffix(text.substring(i) + WORD_TERMINATION, i); + } + fullText += text; + } + + public List searchText(String pattern) { + LOGGER.info("Searching for pattern \"{}\"", pattern); + List result = new ArrayList<>(); + List nodes = getAllNodesInTraversePath(pattern, root, false); + + if (nodes.size() > 0) { + Node lastNode = nodes.get(nodes.size() - 1); + if (lastNode != null) { + List positions = getPositions(lastNode); + positions = positions.stream() + .sorted() + .collect(Collectors.toList()); + positions.forEach(m -> result.add((markPatternInText(m, pattern)))); + } + } + return result; + } + + private void addSuffix(String suffix, int position) { + LOGGER.info(">>>>>>>>>>>> Adding new suffix {}", suffix); + List nodes = getAllNodesInTraversePath(suffix, root, true); + if (nodes.size() == 0) { + addChildNode(root, suffix, position); + LOGGER.info("{}", printTree()); + } else { + Node lastNode = nodes.remove(nodes.size() - 1); + String newText = suffix; + if (nodes.size() > 0) { + String existingSuffixUptoLastNode = nodes.stream() + .map(a -> a.getText()) + .reduce("", String::concat); + + // Remove prefix from newText already included in parent + newText = newText.substring(existingSuffixUptoLastNode.length()); + } + extendNode(lastNode, newText, position); + LOGGER.info("{}", printTree()); + } + } + + private List getPositions(Node node) { + List positions = new ArrayList<>(); + if (node.getText() + .endsWith(WORD_TERMINATION)) { + positions.add(node.getPosition()); + } + for (int i = 0; i < node.getChildren() + .size(); i++) { + positions.addAll(getPositions(node.getChildren() + .get(i))); + } + return positions; + } + + private String markPatternInText(Integer startPosition, String pattern) { + String matchingTextLHS = fullText.substring(0, startPosition); + String matchingText = fullText.substring(startPosition, startPosition + pattern.length()); + String matchingTextRHS = fullText.substring(startPosition + pattern.length()); + return matchingTextLHS + "[" + matchingText + "]" + matchingTextRHS; + } + + private void addChildNode(Node parentNode, String text, int position) { + parentNode.getChildren() + .add(new Node(text, position)); + } + + private void extendNode(Node node, String newText, int position) { + String currentText = node.getText(); + String commonPrefix = getLongestCommonPrefix(currentText, newText); + + if (commonPrefix != currentText) { + String parentText = currentText.substring(0, commonPrefix.length()); + String childText = currentText.substring(commonPrefix.length()); + splitNodeToParentAndChild(node, parentText, childText); + } + + String remainingText = newText.substring(commonPrefix.length()); + addChildNode(node, remainingText, position); + } + + private void splitNodeToParentAndChild(Node parentNode, String parentNewText, String childNewText) { + Node childNode = new Node(childNewText, parentNode.getPosition()); + + if (parentNode.getChildren() + .size() > 0) { + while (parentNode.getChildren() + .size() > 0) { + childNode.getChildren() + .add(parentNode.getChildren() + .remove(0)); + } + } + + parentNode.getChildren() + .add(childNode); + parentNode.setText(parentNewText); + parentNode.setPosition(POSITION_UNDEFINED); + } + + private String getLongestCommonPrefix(String str1, String str2) { + int compareLength = Math.min(str1.length(), str2.length()); + for (int i = 0; i < compareLength; i++) { + if (str1.charAt(i) != str2.charAt(i)) { + return str1.substring(0, i); + } + } + return str1.substring(0, compareLength); + } + + private List getAllNodesInTraversePath(String pattern, Node startNode, boolean isAllowPartialMatch) { + List nodes = new ArrayList<>(); + for (int i = 0; i < startNode.getChildren() + .size(); i++) { + Node currentNode = startNode.getChildren() + .get(i); + String nodeText = currentNode.getText(); + if (pattern.charAt(0) == nodeText.charAt(0)) { + if (isAllowPartialMatch && pattern.length() <= nodeText.length()) { + nodes.add(currentNode); + return nodes; + } + + int compareLength = Math.min(nodeText.length(), pattern.length()); + for (int j = 1; j < compareLength; j++) { + if (pattern.charAt(j) != nodeText.charAt(j)) { + if (isAllowPartialMatch) + nodes.add(currentNode); + return nodes; + } + } + + nodes.add(currentNode); + if (pattern.length() > compareLength) { + List nodes2 = getAllNodesInTraversePath(pattern.substring(compareLength), currentNode, isAllowPartialMatch); + if (nodes2.size() == 0 && !isAllowPartialMatch) { + nodes.add(null); + return nodes; + } + nodes.addAll(nodes2); + } + return nodes; + } + } + return nodes; + } + + public String printTree() { + return root.printTree(""); + } +} diff --git a/algorithms-searching/src/test/java/com/baeldung/algorithms/suffixtree/SuffixTreeUnitTest.java b/algorithms-searching/src/test/java/com/baeldung/algorithms/suffixtree/SuffixTreeUnitTest.java new file mode 100644 index 0000000000..1e08b2bf44 --- /dev/null +++ b/algorithms-searching/src/test/java/com/baeldung/algorithms/suffixtree/SuffixTreeUnitTest.java @@ -0,0 +1,78 @@ +package com.baeldung.algorithms.suffixtree; + +import java.util.List; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SuffixTreeUnitTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(SuffixTreeUnitTest.class); + + private static SuffixTree suffixTree; + + @BeforeClass + public static void setUp() { + suffixTree = new SuffixTree(); + suffixTree.addText("bananabanana"); + printTree(); + } + + @Test + public void givenSuffixTree_whenSearchingForA_thenReturn6Matches() { + List matches = suffixTree.searchText("a"); + matches.stream() + .forEach(m -> LOGGER.info(m)); + Assert.assertArrayEquals(new String[] { "b[a]nanabanana", "ban[a]nabanana", "banan[a]banana", "bananab[a]nana", "bananaban[a]na", "bananabanan[a]" }, matches.toArray()); + } + + @Test + public void givenSuffixTree_whenSearchingForNab_thenReturn1Match() { + List matches = suffixTree.searchText("nab"); + matches.stream() + .forEach(m -> LOGGER.info(m)); + Assert.assertArrayEquals(new String[] { "bana[nab]anana" }, matches.toArray()); + } + + @Test + public void givenSuffixTree_whenSearchingForNag_thenReturnNoMatches() { + List matches = suffixTree.searchText("nag"); + matches.stream() + .forEach(m -> LOGGER.info(m)); + Assert.assertArrayEquals(new String[] {}, matches.toArray()); + } + + @Test + public void givenSuffixTree_whenSearchingForBanana_thenReturn2Matches() { + List matches = suffixTree.searchText("banana"); + matches.stream() + .forEach(m -> LOGGER.info(m)); + Assert.assertArrayEquals(new String[] { "[banana]banana", "banana[banana]" }, matches.toArray()); + } + + @Test + public void givenSuffixTree_whenSearchingForNa_thenReturn4Matches() { + List matches = suffixTree.searchText("na"); + matches.stream() + .forEach(m -> LOGGER.info(m)); + Assert.assertArrayEquals(new String[] { "ba[na]nabanana", "bana[na]banana", "bananaba[na]na", "bananabana[na]" }, matches.toArray()); + } + + @Test + public void givenSuffixTree_whenSearchingForX_thenReturnNoMatches() { + List matches = suffixTree.searchText("x"); + matches.stream() + .forEach(m -> LOGGER.info(m)); + Assert.assertArrayEquals(new String[] {}, matches.toArray()); + } + + private static void printTree() { + suffixTree.printTree(); + + LOGGER.info("\n" + suffixTree.printTree()); + LOGGER.info("=============================================="); + } +} From ee114c8a173dab592d08ccf69da24a4318e89ac1 Mon Sep 17 00:00:00 2001 From: macroscopic64 Date: Sun, 23 Feb 2020 20:55:32 +0530 Subject: [PATCH 5/5] [BAEL-3480] - Java Fast pattern matching using trie and suffix tree --- .../algorithms/suffixtree/SuffixTree.java | 17 +++++++---------- .../suffixtree/SuffixTreeUnitTest.java | 13 ++++++------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/algorithms-searching/src/main/java/com/baeldung/algorithms/suffixtree/SuffixTree.java b/algorithms-searching/src/main/java/com/baeldung/algorithms/suffixtree/SuffixTree.java index 50d9702cd3..eb58c2bfab 100644 --- a/algorithms-searching/src/main/java/com/baeldung/algorithms/suffixtree/SuffixTree.java +++ b/algorithms-searching/src/main/java/com/baeldung/algorithms/suffixtree/SuffixTree.java @@ -14,16 +14,12 @@ public class SuffixTree { private Node root; private String fullText; - public SuffixTree() { + public SuffixTree(String text) { root = new Node("", POSITION_UNDEFINED); - fullText = ""; - } - - public void addText(String text) { for (int i = 0; i < text.length(); i++) { addSuffix(text.substring(i) + WORD_TERMINATION, i); } - fullText += text; + fullText = text; } public List searchText(String pattern) { @@ -151,8 +147,9 @@ public class SuffixTree { int compareLength = Math.min(nodeText.length(), pattern.length()); for (int j = 1; j < compareLength; j++) { if (pattern.charAt(j) != nodeText.charAt(j)) { - if (isAllowPartialMatch) + if (isAllowPartialMatch) { nodes.add(currentNode); + } return nodes; } } @@ -160,11 +157,11 @@ public class SuffixTree { nodes.add(currentNode); if (pattern.length() > compareLength) { List nodes2 = getAllNodesInTraversePath(pattern.substring(compareLength), currentNode, isAllowPartialMatch); - if (nodes2.size() == 0 && !isAllowPartialMatch) { + if (nodes2.size() > 0) { + nodes.addAll(nodes2); + } else if (!isAllowPartialMatch) { nodes.add(null); - return nodes; } - nodes.addAll(nodes2); } return nodes; } diff --git a/algorithms-searching/src/test/java/com/baeldung/algorithms/suffixtree/SuffixTreeUnitTest.java b/algorithms-searching/src/test/java/com/baeldung/algorithms/suffixtree/SuffixTreeUnitTest.java index 1e08b2bf44..ef4a05a9a1 100644 --- a/algorithms-searching/src/test/java/com/baeldung/algorithms/suffixtree/SuffixTreeUnitTest.java +++ b/algorithms-searching/src/test/java/com/baeldung/algorithms/suffixtree/SuffixTreeUnitTest.java @@ -16,8 +16,7 @@ public class SuffixTreeUnitTest { @BeforeClass public static void setUp() { - suffixTree = new SuffixTree(); - suffixTree.addText("bananabanana"); + suffixTree = new SuffixTree("havanabanana"); printTree(); } @@ -26,7 +25,7 @@ public class SuffixTreeUnitTest { List matches = suffixTree.searchText("a"); matches.stream() .forEach(m -> LOGGER.info(m)); - Assert.assertArrayEquals(new String[] { "b[a]nanabanana", "ban[a]nabanana", "banan[a]banana", "bananab[a]nana", "bananaban[a]na", "bananabanan[a]" }, matches.toArray()); + Assert.assertArrayEquals(new String[] { "h[a]vanabanana", "hav[a]nabanana", "havan[a]banana", "havanab[a]nana", "havanaban[a]na", "havanabanan[a]" }, matches.toArray()); } @Test @@ -34,7 +33,7 @@ public class SuffixTreeUnitTest { List matches = suffixTree.searchText("nab"); matches.stream() .forEach(m -> LOGGER.info(m)); - Assert.assertArrayEquals(new String[] { "bana[nab]anana" }, matches.toArray()); + Assert.assertArrayEquals(new String[] { "hava[nab]anana" }, matches.toArray()); } @Test @@ -47,10 +46,10 @@ public class SuffixTreeUnitTest { @Test public void givenSuffixTree_whenSearchingForBanana_thenReturn2Matches() { - List matches = suffixTree.searchText("banana"); + List matches = suffixTree.searchText("ana"); matches.stream() .forEach(m -> LOGGER.info(m)); - Assert.assertArrayEquals(new String[] { "[banana]banana", "banana[banana]" }, matches.toArray()); + Assert.assertArrayEquals(new String[] { "hav[ana]banana", "havanab[ana]na", "havanaban[ana]" }, matches.toArray()); } @Test @@ -58,7 +57,7 @@ public class SuffixTreeUnitTest { List matches = suffixTree.searchText("na"); matches.stream() .forEach(m -> LOGGER.info(m)); - Assert.assertArrayEquals(new String[] { "ba[na]nabanana", "bana[na]banana", "bananaba[na]na", "bananabana[na]" }, matches.toArray()); + Assert.assertArrayEquals(new String[] { "hava[na]banana", "havanaba[na]na", "havanabana[na]" }, matches.toArray()); } @Test