From 8fab22b6e72b2a90ddb30f875a596128002df4bc Mon Sep 17 00:00:00 2001 From: macroscopic64 <46401144+macroscopic64@users.noreply.github.com> Date: Fri, 27 Dec 2019 23:34:59 +0530 Subject: [PATCH] [BAEL-3485] - Java Range lookup problem (#8392) * [BAEL-3485] - Java Range lookup problem * [BAEL-3485] - Java Range lookup problem * [BAEL-3485] - Java Range lookup problem --- .../baeldung/algorithms/quadtree/Point.java | 24 ++++ .../algorithms/quadtree/QuadTree.java | 109 ++++++++++++++++++ .../baeldung/algorithms/quadtree/Region.java | 85 ++++++++++++++ .../quadtree/QuadTreeSearchUnitTest.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/QuadTreeSearchUnitTest.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/QuadTreeSearchUnitTest.java b/algorithms-searching/src/test/java/com/baeldung/algorithms/quadtree/QuadTreeSearchUnitTest.java new file mode 100644 index 0000000000..0b58ae9f14 --- /dev/null +++ b/algorithms-searching/src/test/java/com/baeldung/algorithms/quadtree/QuadTreeSearchUnitTest.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 QuadTreeSearchUnitTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(QuadTreeSearchUnitTest.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.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.info(result.toString()); + LOGGER.info(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.info(result.toString()); + LOGGER.info(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); + + } +}